diff options
author | Luis Sigal <luissigal@google.com> | 2011-02-24 17:22:41 +0000 |
---|---|---|
committer | Luis Sigal <luissigal@google.com> | 2011-02-24 18:30:29 +0000 |
commit | 69e17611504376e4d4603925f8528dfc890fd2c6 (patch) | |
tree | dc23f16c689d079496c05fe8b990b033b4d5821a | |
parent | b93c96de4ceae359025c21b13dfee692971cdc00 (diff) | |
download | javassist-69e17611504376e4d4603925f8528dfc890fd2c6.tar.gz |
Add javassist to external
We are going to add android-mock to external, which uses javassist.
Change-Id: Ibc17ea987c3a8cebb0a30e56d3574c3bacd589b2
281 files changed, 66050 insertions, 0 deletions
diff --git a/Android.mk b/Android.mk new file mode 100644 index 0000000..b5d12be --- /dev/null +++ b/Android.mk @@ -0,0 +1,30 @@ +# +# Copyright (C) 2011 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) + +# We only want this apk build for tests. +LOCAL_MODULE_TAGS := tests + +# We need classes from the tools dir in the JDK +LOCAL_CLASSPATH := $(HOST_JDK_TOOLS_JAR) + +LOCAL_MODULE := javassistlib + +LOCAL_SRC_FILES := $(call all-java-files-under,src/main) + +include $(BUILD_HOST_JAVA_LIBRARY) diff --git a/HOWTO.txt b/HOWTO.txt new file mode 100644 index 0000000..91f9992 --- /dev/null +++ b/HOWTO.txt @@ -0,0 +1,4 @@ +In order to update the code, simply export it from svn with the regenerate_from_source.sh. +If you want to run the tests, remember to do it from master or a branch that +includes junit.jar and that has been compiled. The command is: +ant -propertyfile build.properties test diff --git a/License.html b/License.html new file mode 100644 index 0000000..bec1335 --- /dev/null +++ b/License.html @@ -0,0 +1,372 @@ +<HTML> +<HEAD> +<TITLE>Javassist License</TITLE> +<META http-equiv=Content-Type content="text/html; charset=iso-8859-1"> +<META content="MSHTML 5.50.4916.2300" name=GENERATOR></HEAD> + +<BODY text=#000000 vLink=#551a8b aLink=#ff0000 link=#0000ee bgColor=#ffffff> +<CENTER><B><FONT size=+2>MOZILLA PUBLIC LICENSE</FONT></B> <BR><B>Version +1.1</B> +<P> +<HR width="20%"> +</CENTER> +<P><B>1. Definitions.</B> +<UL><B>1.0.1. "Commercial Use" </B>means distribution or otherwise making the + Covered Code available to a third party. + <P><B>1.1. ''Contributor''</B> means each entity that creates or contributes + to the creation of Modifications. + <P><B>1.2. ''Contributor Version''</B> means the combination of the Original + Code, prior Modifications used by a Contributor, and the Modifications made by + that particular Contributor. + <P><B>1.3. ''Covered Code''</B> means the Original Code or Modifications or + the combination of the Original Code and Modifications, in each case including + portions thereof<B>.</B> + <P><B>1.4. ''Electronic Distribution Mechanism''</B> means a mechanism + generally accepted in the software development community for the electronic + transfer of data. + <P><B>1.5. ''Executable''</B> means Covered Code in any form other than Source + Code. + <P><B>1.6. ''Initial Developer''</B> means the individual or entity identified + as the Initial Developer in the Source Code notice required by <B>Exhibit + A</B>. + <P><B>1.7. ''Larger Work''</B> means a work which combines Covered Code or + portions thereof with code not governed by the terms of this License. + <P><B>1.8. ''License''</B> means this document. + <P><B>1.8.1. "Licensable"</B> means having the right to grant, to the maximum + extent possible, whether at the time of the initial grant or subsequently + acquired, any and all of the rights conveyed herein. + <P><B>1.9. ''Modifications''</B> means any addition to or deletion from the + substance or structure of either the Original Code or any previous + Modifications. When Covered Code is released as a series of files, a + Modification is: + <UL><B>A.</B> Any addition to or deletion from the contents of a file + containing Original Code or previous Modifications. + <P><B>B.</B> Any new file that contains any part of the Original Code or + previous Modifications. <BR> </P></UL><B>1.10. ''Original Code''</B> + means Source Code of computer software code which is described in the Source + Code notice required by <B>Exhibit A</B> as Original Code, and which, at the + time of its release under this License is not already Covered Code governed by + this License. + <P><B>1.10.1. "Patent Claims"</B> means any patent claim(s), now owned or + hereafter acquired, including without limitation, method, process, and + apparatus claims, in any patent Licensable by grantor. + <P><B>1.11. ''Source Code''</B> means the preferred form of the Covered Code + for making modifications to it, including all modules it contains, plus any + associated interface definition files, scripts used to control compilation and + installation of an Executable, or source code differential comparisons against + either the Original Code or another well known, available Covered Code of the + Contributor's choice. The Source Code can be in a compressed or archival form, + provided the appropriate decompression or de-archiving software is widely + available for no charge. + <P><B>1.12. "You'' (or "Your") </B> means an individual or a legal entity + exercising rights under, and complying with all of the terms of, this License + or a future version of this License issued under Section 6.1. For legal + entities, "You'' includes any entity which controls, is controlled by, or is + under common control with You. For purposes of this definition, "control'' + means (a) the power, direct or indirect, to cause the direction or management + of such entity, whether by contract or otherwise, or (b) ownership of more + than fifty percent (50%) of the outstanding shares or beneficial ownership of + such entity.</P></UL><B>2. Source Code License.</B> +<UL><B>2.1. The Initial Developer Grant.</B> <BR>The Initial Developer hereby + grants You a world-wide, royalty-free, non-exclusive license, subject to third + party intellectual property claims: + <UL><B>(a)</B> <B> </B>under intellectual property rights (other than + patent or trademark) Licensable by Initial Developer to use, reproduce, + modify, display, perform, sublicense and distribute the Original Code (or + portions thereof) with or without Modifications, and/or as part of a Larger + Work; and + <P><B>(b)</B> under Patents Claims infringed by the making, using or selling + of Original Code, to make, have made, use, practice, sell, and offer for + sale, and/or otherwise dispose of the Original Code (or portions thereof). + <UL> + <UL></UL></UL><B>(c) </B>the licenses granted in this Section 2.1(a) and (b) + are effective on the date Initial Developer first distributes Original Code + under the terms of this License. + <P><B>(d) </B>Notwithstanding Section 2.1(b) above, no patent license is + granted: 1) for code that You delete from the Original Code; 2) separate + from the Original Code; or 3) for infringements caused by: i) the + modification of the Original Code or ii) the combination of the Original + Code with other software or devices. <BR> </P></UL><B>2.2. Contributor + Grant.</B> <BR>Subject to third party intellectual property claims, each + Contributor hereby grants You a world-wide, royalty-free, non-exclusive + license + <UL> <BR><B>(a)</B> <B> </B>under intellectual property rights (other + than patent or trademark) Licensable by Contributor, to use, reproduce, + modify, display, perform, sublicense and distribute the Modifications + created by such Contributor (or portions thereof) either on an unmodified + basis, with other Modifications, as Covered Code and/or as part of a Larger + Work; and + <P><B>(b)</B> under Patent Claims infringed by the making, using, or selling + of Modifications made by that Contributor either alone and/or in<FONT + color=#000000> combination with its Contributor Version (or portions of such + combination), to make, use, sell, offer for sale, have made, and/or + otherwise dispose of: 1) Modifications made by that Contributor (or portions + thereof); and 2) the combination of Modifications made by that + Contributor with its Contributor Version (or portions of such + combination).</FONT> + <P><B>(c) </B>the licenses granted in Sections 2.2(a) and 2.2(b) are + effective on the date Contributor first makes Commercial Use of the Covered + Code. + <P><B>(d) </B> Notwithstanding Section 2.2(b) above, no + patent license is granted: 1) for any code that Contributor has deleted from + the Contributor Version; 2) separate from the Contributor + Version; 3) for infringements caused by: i) third party + modifications of Contributor Version or ii) the combination of + Modifications made by that Contributor with other software (except as + part of the Contributor Version) or other devices; or 4) under Patent Claims + infringed by Covered Code in the absence of Modifications made by that + Contributor.</P></UL></UL> +<P><BR><B>3. Distribution Obligations.</B> +<UL><B>3.1. Application of License.</B> <BR>The Modifications which You create + or to which You contribute are governed by the terms of this License, + including without limitation Section <B>2.2</B>. The Source Code version of + Covered Code may be distributed only under the terms of this License or a + future version of this License released under Section <B>6.1</B>, and You must + include a copy of this License with every copy of the Source Code You + distribute. You may not offer or impose any terms on any Source Code version + that alters or restricts the applicable version of this License or the + recipients' rights hereunder. However, You may include an additional document + offering the additional rights described in Section <B>3.5</B>. + <P><B>3.2. Availability of Source Code.</B> <BR>Any Modification which You + create or to which You contribute must be made available in Source Code form + under the terms of this License either on the same media as an Executable + version or via an accepted Electronic Distribution Mechanism to anyone to whom + you made an Executable version available; and if made available via Electronic + Distribution Mechanism, must remain available for at least twelve (12) months + after the date it initially became available, or at least six (6) months after + a subsequent version of that particular Modification has been made available + to such recipients. You are responsible for ensuring that the Source Code + version remains available even if the Electronic Distribution Mechanism is + maintained by a third party. + <P><B>3.3. Description of Modifications.</B> <BR>You must cause all Covered + Code to which You contribute to contain a file documenting the changes You + made to create that Covered Code and the date of any change. You must include + a prominent statement that the Modification is derived, directly or + indirectly, from Original Code provided by the Initial Developer and including + the name of the Initial Developer in (a) the Source Code, and (b) in any + notice in an Executable version or related documentation in which You describe + the origin or ownership of the Covered Code. + <P><B>3.4. Intellectual Property Matters</B> + <UL><B>(a) Third Party Claims</B>. <BR>If Contributor has knowledge that a + license under a third party's intellectual property rights is required to + exercise the rights granted by such Contributor under Sections 2.1 or 2.2, + Contributor must include a text file with the Source Code distribution + titled "LEGAL'' which describes the claim and the party making the claim in + sufficient detail that a recipient will know whom to contact. If Contributor + obtains such knowledge after the Modification is made available as described + in Section 3.2, Contributor shall promptly modify the LEGAL file in all + copies Contributor makes available thereafter and shall take other steps + (such as notifying appropriate mailing lists or newsgroups) reasonably + calculated to inform those who received the Covered Code that new knowledge + has been obtained. + <P><B>(b) Contributor APIs</B>. <BR>If Contributor's Modifications include + an application programming interface and Contributor has knowledge of patent + licenses which are reasonably necessary to implement that API, Contributor + must also include this information in the LEGAL file. + <BR> </P></UL> + <B>(c) Representations.</B> + <UL>Contributor represents that, except as disclosed pursuant to Section + 3.4(a) above, Contributor believes that Contributor's Modifications are + Contributor's original creation(s) and/or Contributor has sufficient rights + to grant the rights conveyed by this License.</UL> + <P><BR><B>3.5. Required Notices.</B> <BR>You must duplicate the notice in + <B>Exhibit A</B> in each file of the Source Code. If it is not possible + to put such notice in a particular Source Code file due to its structure, then + You must include such notice in a location (such as a relevant directory) + where a user would be likely to look for such a notice. If You created + one or more Modification(s) You may add your name as a Contributor to the + notice described in <B>Exhibit A</B>. You must also duplicate this + License in any documentation for the Source Code where You describe + recipients' rights or ownership rights relating to Covered Code. You may + choose to offer, and to charge a fee for, warranty, support, indemnity or + liability obligations to one or more recipients of Covered Code. However, You + may do so only on Your own behalf, and not on behalf of the Initial Developer + or any Contributor. You must make it absolutely clear than any such warranty, + support, indemnity or liability obligation is offered by You alone, and You + hereby agree to indemnify the Initial Developer and every Contributor for any + liability incurred by the Initial Developer or such Contributor as a result of + warranty, support, indemnity or liability terms You offer. + <P><B>3.6. Distribution of Executable Versions.</B> <BR>You may distribute + Covered Code in Executable form only if the requirements of Section + <B>3.1-3.5</B> have been met for that Covered Code, and if You include a + notice stating that the Source Code version of the Covered Code is available + under the terms of this License, including a description of how and where You + have fulfilled the obligations of Section <B>3.2</B>. The notice must be + conspicuously included in any notice in an Executable version, related + documentation or collateral in which You describe recipients' rights relating + to the Covered Code. You may distribute the Executable version of Covered Code + or ownership rights under a license of Your choice, which may contain terms + different from this License, provided that You are in compliance with the + terms of this License and that the license for the Executable version does not + attempt to limit or alter the recipient's rights in the Source Code version + from the rights set forth in this License. If You distribute the Executable + version under a different license You must make it absolutely clear that any + terms which differ from this License are offered by You alone, not by the + Initial Developer or any Contributor. You hereby agree to indemnify the + Initial Developer and every Contributor for any liability incurred by the + Initial Developer or such Contributor as a result of any such terms You offer. + + <P><B>3.7. Larger Works.</B> <BR>You may create a Larger Work by combining + Covered Code with other code not governed by the terms of this License and + distribute the Larger Work as a single product. In such a case, You must make + sure the requirements of this License are fulfilled for the Covered +Code.</P></UL><B>4. Inability to Comply Due to Statute or Regulation.</B> +<UL>If it is impossible for You to comply with any of the terms of this + License with respect to some or all of the Covered Code due to statute, + judicial order, or regulation then You must: (a) comply with the terms of this + License to the maximum extent possible; and (b) describe the limitations and + the code they affect. Such description must be included in the LEGAL file + described in Section <B>3.4</B> and must be included with all distributions of + the Source Code. Except to the extent prohibited by statute or regulation, + such description must be sufficiently detailed for a recipient of ordinary + skill to be able to understand it.</UL><B>5. Application of this License.</B> +<UL>This License applies to code to which the Initial Developer has attached + the notice in <B>Exhibit A</B> and to related Covered Code.</UL><B>6. Versions +of the License.</B> +<UL><B>6.1. New Versions</B>. <BR>Netscape Communications Corporation + (''Netscape'') may publish revised and/or new versions of the License from + time to time. Each version will be given a distinguishing version number. + <P><B>6.2. Effect of New Versions</B>. <BR>Once Covered Code has been + published under a particular version of the License, You may always continue + to use it under the terms of that version. You may also choose to use such + Covered Code under the terms of any subsequent version of the License + published by Netscape. No one other than Netscape has the right to modify the + terms applicable to Covered Code created under this License. + <P><B>6.3. Derivative Works</B>. <BR>If You create or use a modified version + of this License (which you may only do in order to apply it to code which is + not already Covered Code governed by this License), You must (a) rename Your + license so that the phrases ''Mozilla'', ''MOZILLAPL'', ''MOZPL'', + ''Netscape'', "MPL", ''NPL'' or any confusingly similar phrase do not appear + in your license (except to note that your license differs from this License) + and (b) otherwise make it clear that Your version of the license contains + terms which differ from the Mozilla Public License and Netscape Public + License. (Filling in the name of the Initial Developer, Original Code or + Contributor in the notice described in <B>Exhibit A</B> shall not of + themselves be deemed to be modifications of this License.)</P></UL><B>7. +DISCLAIMER OF WARRANTY.</B> +<UL>COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS'' BASIS, WITHOUT + WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT + LIMITATION, WARRANTIES THAT THE COVERED CODE IS FREE OF DEFECTS, MERCHANTABLE, + FIT FOR A PARTICULAR PURPOSE OR NON-INFRINGING. THE ENTIRE RISK AS TO THE + QUALITY AND PERFORMANCE OF THE COVERED CODE IS WITH YOU. SHOULD ANY COVERED + CODE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT THE INITIAL DEVELOPER OR ANY + OTHER CONTRIBUTOR) ASSUME THE COST OF ANY NECESSARY SERVICING, REPAIR OR + CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS + LICENSE. NO USE OF ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS + DISCLAIMER.</UL><B>8. TERMINATION.</B> +<UL><B>8.1. </B>This License and the rights granted hereunder will + terminate automatically if You fail to comply with terms herein and fail to + cure such breach within 30 days of becoming aware of the breach. All + sublicenses to the Covered Code which are properly granted shall survive any + termination of this License. Provisions which, by their nature, must remain in + effect beyond the termination of this License shall survive. + <P><B>8.2. </B>If You initiate litigation by asserting a patent + infringement claim (excluding declatory judgment actions) against Initial + Developer or a Contributor (the Initial Developer or Contributor against whom + You file such action is referred to as "Participant") alleging that: + <P><B>(a) </B>such Participant's Contributor Version directly or + indirectly infringes any patent, then any and all rights granted by such + Participant to You under Sections 2.1 and/or 2.2 of this License shall, upon + 60 days notice from Participant terminate prospectively, unless if within 60 + days after receipt of notice You either: (i) agree in writing to pay + Participant a mutually agreeable reasonable royalty for Your past and future + use of Modifications made by such Participant, or (ii) withdraw Your + litigation claim with respect to the Contributor Version against such + Participant. If within 60 days of notice, a reasonable royalty and + payment arrangement are not mutually agreed upon in writing by the parties or + the litigation claim is not withdrawn, the rights granted by Participant to + You under Sections 2.1 and/or 2.2 automatically terminate at the expiration of + the 60 day notice period specified above. + <P><B>(b)</B> any software, hardware, or device, other than such + Participant's Contributor Version, directly or indirectly infringes any + patent, then any rights granted to You by such Participant under Sections + 2.1(b) and 2.2(b) are revoked effective as of the date You first made, used, + sold, distributed, or had made, Modifications made by that Participant. + <P><B>8.3. </B>If You assert a patent infringement claim against + Participant alleging that such Participant's Contributor Version directly or + indirectly infringes any patent where such claim is resolved (such as by + license or settlement) prior to the initiation of patent infringement + litigation, then the reasonable value of the licenses granted by such + Participant under Sections 2.1 or 2.2 shall be taken into account in + determining the amount or value of any payment or license. + <P><B>8.4.</B> In the event of termination under Sections 8.1 or 8.2 + above, all end user license agreements (excluding distributors and + resellers) which have been validly granted by You or any distributor hereunder + prior to termination shall survive termination.</P></UL><B>9. LIMITATION OF +LIABILITY.</B> +<UL>UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT (INCLUDING + NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE INITIAL DEVELOPER, ANY + OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF COVERED CODE, OR ANY SUPPLIER OF ANY + OF SUCH PARTIES, BE LIABLE TO ANY PERSON FOR ANY INDIRECT, SPECIAL, + INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, WITHOUT + LIMITATION, DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR + MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH + PARTY SHALL HAVE BEEN INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS + LIMITATION OF LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL + INJURY RESULTING FROM SUCH PARTY'S NEGLIGENCE TO THE EXTENT APPLICABLE LAW + PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OR + LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO THIS EXCLUSION AND + LIMITATION MAY NOT APPLY TO YOU.</UL><B>10. U.S. GOVERNMENT END USERS.</B> +<UL>The Covered Code is a ''commercial item,'' as that term is defined in 48 + C.F.R. 2.101 (Oct. 1995), consisting of ''commercial computer software'' and + ''commercial computer software documentation,'' as such terms are used in 48 + C.F.R. 12.212 (Sept. 1995). Consistent with 48 C.F.R. 12.212 and 48 C.F.R. + 227.7202-1 through 227.7202-4 (June 1995), all U.S. Government End Users + acquire Covered Code with only those rights set forth herein.</UL><B>11. +MISCELLANEOUS.</B> +<UL>This License represents the complete agreement concerning subject matter + hereof. If any provision of this License is held to be unenforceable, such + provision shall be reformed only to the extent necessary to make it + enforceable. This License shall be governed by California law provisions + (except to the extent applicable law, if any, provides otherwise), excluding + its conflict-of-law provisions. With respect to disputes in which at least one + party is a citizen of, or an entity chartered or registered to do business in + the United States of America, any litigation relating to this License shall be + subject to the jurisdiction of the Federal Courts of the Northern District of + California, with venue lying in Santa Clara County, California, with the + losing party responsible for costs, including without limitation, court costs + and reasonable attorneys' fees and expenses. The application of the United + Nations Convention on Contracts for the International Sale of Goods is + expressly excluded. Any law or regulation which provides that the language of + a contract shall be construed against the drafter shall not apply to this + License.</UL><B>12. RESPONSIBILITY FOR CLAIMS.</B> +<UL>As between Initial Developer and the Contributors, each party is + responsible for claims and damages arising, directly or indirectly, out of its + utilization of rights under this License and You agree to work with Initial + Developer and Contributors to distribute such responsibility on an equitable + basis. Nothing herein is intended or shall be deemed to constitute any + admission of liability.</UL><B>13. MULTIPLE-LICENSED CODE.</B> +<UL>Initial Developer may designate portions of the Covered Code as + �Multiple-Licensed?. �Multiple-Licensed? means that the Initial + Developer permits you to utilize portions of the Covered Code under Your + choice of the MPL or the alternative licenses, if any, specified by the + Initial Developer in the file described in Exhibit A.</UL> +<P><BR><B>EXHIBIT A -Mozilla Public License.</B> +<UL>The contents of this file are subject to the Mozilla Public License + Version 1.1 (the "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + <BR>http://www.mozilla.org/MPL/ + <P>Software distributed under the License is distributed on an "AS IS" basis, + WITHOUT WARRANTY OF <BR>ANY KIND, either express or implied. See the License + for the specific language governing rights and <BR>limitations under the + License. + <P>The Original Code is Javassist. + <P>The Initial Developer of the Original Code is Shigeru Chiba. + Portions created by the Initial Developer are<BR> + Copyright (C) 1999-2010 Shigeru Chiba. All Rights Reserved. + <P>Contributor(s): ______________________________________. + + <P>Alternatively, the contents of this file may be used under the terms of + the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + in which case the provisions of the LGPL are applicable instead + of those above. If you wish to allow use of your version of this file only + under the terms of the LGPL, and not to allow others to + use your version of this file under the terms of the MPL, indicate your + decision by deleting the provisions above and replace them with the notice + and other provisions required by the LGPL. If you do not delete + the provisions above, a recipient may use your version of this file under + the terms of either the MPL or the LGPL. + + <P></P></UL> +</BODY> +</HTML> diff --git a/Readme.html b/Readme.html new file mode 100644 index 0000000..f6429fe --- /dev/null +++ b/Readme.html @@ -0,0 +1,800 @@ +<html> +<HEAD> + <META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=iso-8859-1"> + <TITLE>Read Me First</TITLE> +</HEAD> +<body> + +<h1>Javassist version 3</h1> + +<h3>Copyright (C) 1999-2010 by Shigeru Chiba, All rights reserved.</h3> + +<p><br></p> + +<p>Javassist (JAVA programming ASSISTant) makes Java bytecode manipulation +simple. It is a class library for editing bytecodes in Java; +it enables Java programs to define a new class at runtime and to +modify a class file when the JVM loads it. Unlike other similar +bytecode editors, Javassist provides two levels of API: source level +and bytecode level. If the users use the source-level API, they can +edit a class file without knowledge of the specifications of the Java +bytecode. The whole API is designed with only the vocabulary of the +Java language. You can even specify inserted bytecode in the form of +source text; Javassist compiles it on the fly. On the other hand, the +bytecode-level API allows the users to directly edit a class file as +other editors. + +<p><br> + +<h2>Files</h2> + +<ul> +<table> +<tr> +<td><li><tt><a href="License.html">License.html</a></tt></td> +<td>License file +(Also see the <a href="#copyright">copyright notices</a> below)</td> +</tr> + +<tr> +<td><li><tt><a href="tutorial/tutorial.html">tutorial/tutorial.html</a></tt></td> +<td>Tutorial</td> +</tr> + +<tr> +<td><li><tt>./javassist.jar</tt></td> +<td>The Javassist jar file (class files)</td> +</tr> + +<tr> +<td><li><tt>./src/main</tt></td> +<td>The source files</td> +</tr> + +<tr> +<td><li><tt><a href="html/index.html">html/index.html</a></tt></td> +<td>The top page of the Javassist API document.</td> +</tr> + +<tr> +<td><li><tt>./sample/</tt></td> +<td>Sample programs</td> +</tr> +</table> +</ul> + +<p><br> + +<h2>How to run sample programs</h2> + +<p>JDK 1.4 or later is needed. + +<h3>0. If you have Apache Ant</h3> + +<p>Run the sample-all task. +Otherwise, follow the instructions below. + +<h3>1. Move to the directory where this Readme.html file is located.</h3> + +<p>In the following instructions, we assume that the javassist.jar +file is included in the class path. +For example, the javac and java commands must receive +the following <code>classpath</code> option: + +<ul><pre> +-classpath ".:javassist.jar" +</pre></ul> + +<p>If the operating system is Windows, the path +separator must be not <code>:</code> (colon) but +<code>;</code> (semicolon). The java command can receive +the <code>-cp</code> option +as well as <code>-classpath</code>. + +<p>If you don't want to use the class-path option, you can make +<code>javassist.jar</code> included in the <code>CLASSPATH</code> +environment: + +<ul><pre> +export CLASSPATH=.:javassist.jar +</pre></ul> + +<p>or if the operating system is Windows: + +<ul><pre> +set CLASSPATH=.;javassist.jar +</pre></ul> + + +<p>Otherwise, you can copy <tt>javassist.jar</tt> to the directory + +<ul><<i>java-home</i>><tt>/jre/lib/ext</tt>.</ul> + +<p><<i>java-home</i>> depends on the system. It is usually +<tt>/usr/local/java</tt>, <tt>c:\j2sdk1.4\</tt>, etc. + +<h3>2. sample/Test.java</h3> + +<p> This is a very simple program using Javassist. + +<p> To run, type the commands: + +<ul><pre> +% javac sample/Test.java +% java sample.Test +</pre></ul> + +<p> For more details, see <a type="text/plain" href="sample/Test.java">sample/Test.java</a> + +<h3>3. sample/reflect/*.java</h3> + +<p> This is the "verbose metaobject" example well known in reflective + programming. This program dynamically attaches a metaobject to + a Person object. The metaobject prints a message if a method + is called on the Person object. + +<p> To run, type the commands: + +<ul><pre> +% javac sample/reflect/*.java +% java javassist.tools.reflect.Loader sample.reflect.Main Joe +</pre></ul> + +<p>Compare this result with that of the regular execution without reflection: + +<ul><pre>% java sample.reflect.Person Joe</pre></ul> + +<p> For more details, see <a type="text/plain" href="sample/reflect/Main.java">sample/reflect/Main.java</a> + +<p> Furthermore, the Person class can be statically modified so that + all the Person objects become reflective without sample.reflect.Main. + To do this, type the commands: + +<ul><pre> +% java javassist.tools.reflect.Compiler sample.reflect.Person -m sample.reflect.VerboseMetaobj +</pre></ul> + +<p> Then, +<ul><pre> +% java sample.reflect.Person Joe +</pre></ul> + +<h3>4. sample/duplicate/*.java</h3> + +<p> This is another example of reflective programming. + +<p> To run, type the commands: + +<ul><pre> +% javac sample/duplicate/*.java +% java sample.duplicate.Main +</pre></ul> + +<p>Compare this result with that of the regular execution without reflection: + +<ul><pre>% java sample.duplicate.Viewer</pre></ul> + +<p>For more details, see +<a type="text/plain" href="sample/duplicate/Main.java">sample/duplicate/Main.java</a> + +<h3>5. sample/vector/*.java</h3> + +<p>This example shows the use of Javassit for producing a class representing +a vector of a given type at compile time. + +<p> To run, type the commands: +<ul><pre> +% javac sample/vector/*.java +% java sample.preproc.Compiler sample/vector/Test.j +% javac sample/vector/Test.java +% java sample.vector.Test +</pre></ul> + +<p>Note: <code>javassist.jar</code> is unnecessary to compile and execute +<code>sample/vector/Test.java</code>. + +For more details, see +<a type="text/plain" href="sample/vector/Test.j">sample/vector/Test.j</a> +and <a type="text/plain" href="sample/vector/VectorAssistant.java">sample/vector/VectorAssistant.java</a> + +<h3>6. sample/rmi/*.java</h3> + +<p> This demonstrates the javassist.rmi package. + +<p> To run, type the commands: +<ul><pre> +% javac sample/rmi/*.java +% java sample.rmi.Counter 5001 +</pre></ul> + +<p> The second line starts a web server listening to port 5001. + +<p> Then, open <a href="sample/rmi/webdemo.html">sample/rmi/webdemo.html</a> +with a web browser running + on the local host. (<tt>webdemo.html</tt> trys to fetch an applet from + <tt>http://localhost:5001/</tt>, which is the web server we started above.) + +<p> Otherwise, run sample.rmi.CountApplet as an application: + +<ul><pre> +% java javassist.web.Viewer localhost 5001 sample.rmi.CountApplet +</pre></ul> + +<h3>7. sample/evolve/*.java</h3> + +<p> This is a demonstration of the class evolution mechanism implemented + with Javassist. This mechanism enables a Java program to reload an + existing class file under some restriction. + +<p> To run, type the commands: +<ul><pre> +% javac sample/evolve/*.java +% java sample.evolve.DemoLoader 5003 +</pre></ul> + +<p> The second line starts a class loader DemoLoader, which runs a web + server DemoServer listening to port 5003. + +<p> Then, open <a href="http://localhost:5003/demo.html">http://localhost:5003/demo.html</a> with a web browser running + on the local host. +(Or, see <a href="sample/evolve/start.html">sample/evolve/start.html</a>.) + +<h3>8. sample/hotswap/*.java</h3> + +<p>This shows dynamic class reloading by the JPDA. It needs JDK 1.4 or later. +To run, first type the following commands: + +<ul><pre> +% cd sample/hotswap +% javac *.java +% cd logging +% javac *.java +% cd .. +</pre></ul> + +<p>If your Java is 1.4, then type: + +<ul><pre> +% java -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8000 Test +</pre></ul> + +<p>If you are using Java 5, then type: + +<ul><pre> +% java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8000 Test +</pre></ul> + +<p>Note that the class path must include <code>JAVA_HOME/lib/tools.jar</code>. + +<h2>Hints</h2> + +<p>To know the version number, type this command: + +<ul><pre> +% java -jar javassist.jar +</pre></ul> + +<p>Javassist provides a class file viewer for debugging. For more details, +see javassist.Dump. + +<p><br> + +<h2>Changes</h2> + +<p>-version 3.14 on October 5, 2010 + +<ul> + <li>JIRA JASSIST-121, 123, 128, 129, 130, 131, 132. +</ul> + +<p>-version 3.13 on July 19, 2010 + +<ul> + <li>JIRA JASSIST-118, 119, 122, 124, 125. +</ul> + +<p>-version 3.12.1 on June 10, 2010 + +<p>-version 3.12 on April 16, 2010 + +<p>-version 3.11 on July 3, 2009 +<ul> + <li>JIRA JASSIST-67, 68, 74, 75, 76, 77, 81, 83, 84, 85, 86, 87 were fixed. + <li>Now javassist.bytecode.CodeIterator can insert a gap into + a large method body more than 32KB. (JIRA JASSIST-79, 80) +</ul> + +<p>-version 3.10 on March 5, 2009 + +<ul> + <li>JIRA JASSIST-69, 70, 71 were fixed. +</ul> + +<p>-version 3.9 on October 9, 2008 +<ul> + <li>ClassPool.makeClassIfNew(InputStream) was implemented. + <li>CtNewMethod.wrapped(..) and CtNewConstructor.wrapped(..) + implicitly append a method like _added_m$0. + This method now has a synthetic attribute. + <li>JIRA JASSIST-66 has been fixed. +</ul> + +<p>-version 3.8.1 on July 17, 2008 +<ul> + <li>CtClass.rebuildClassFile() has been added. + <li>A few bugs of javassist.bytecode.analysis have been fixed. + 3.8.0 could not correctly deal with one letter class name + such as I and J. +</ul> + +<p>-version 3.8.0 on June 13, 2008 +<ul> + <li>javassist.bytecode.analysis was implemented. + <li>JASSIST-45, 47, 51, 54-57, 60, 62 were fixed. +</ul> + +<p>-version 3.7.1 on March 10, 2008 +<ul> + <li>a bug of javassist.util.proxy has been fixed. +</ul> + +<p>-version 3.7 on January 20, 2008 +<ul> + <li>Several minor bugs have been fixed. +</ul> + +<p>-version 3.6.0 on September 13, 2007 + +<p>-version 3.6.0.CR1 on July 27, 2007 + +<ul> + <li>The stack map table introduced since Java 6 has been supported. + <li>CtClass#getDeclaredBehaviors() now returns a class initializer + as well as methods and constructors. + <li>The default status of automatic pruning was made off. + Instead of pruning, this version of Javassist compresses + the data structure of a class file after toBytecode() is called. + The compressed class file is automatically decompressed when needed. + This saves memory space better than pruning. + <li><a href="http://jira.jboss.com/jira/browse/JASSIST-33">JIRA JASSIST-33</a> has been fixed. +</ul> + +<p>-version 3.5 on April 29, 2007 +<ul> + <li>Various minor updates. +</ul> + +<p>-version 3.4 on November 17, 2006 +<ul> + <li>A bug in CodeConverter#replaceFieldRead() and CodeConverter#replaceFieldWrite() + was fixed. <a href="http://jira.jboss.com/jira/browse/JBAOP-284">JBAOP-284</a>. + + <li>A synchronization bug and a performance bug in <code>javassist.util.proxy</code> + have been fixed + (<a href="http://jira.jboss.com/jira/browse/JASSIST-28">JASSIST-28</a>). + Now generated proxy classes are cached. To turn the caching off, + set <code>ProxyFactory.useCache</code> to <code>false</code>. +</ul> + +<p>-version 3.3 on August 17, 2006 +<ul> + <li>CtClass#toClass() and ClassPool#toClass() were modified to accept a + <code>ProtectionDomain</code> + (<a href="http://jira.jboss.com/jira/browse/JASSIST-23">JASSIST-23</a>). + Now ClassPool#toClass(CtClass, ClassLoader) should not be overridden. All + subclasses of ClassPool must override toClass(CtClass, ClassLoader, + ProtectionDomain). + + <li>CtClass#getAvailableAnnotations() etc. have been implemented. + + <li>A bug related to a way of dealing with a bridge method was fixed + (<a href="http://jira.jboss.com/jira/browse/HIBERNATE-37">HIBERNATE-37</a>). + + <li>javassist.scopedpool package was added. +</ul> + +<p>-version 3.2 on June 21, 2006 + +<ul> + <li>The behavior of CtBehavior#getParameterAnnotations() has been changed. + It is now compatible to Java Reflection API + (<a href="http://jira.jboss.com/jira/browse/JASSIST-19">JASSIST-19</a>). +</ul> + +<p>- version 3.2.0.CR2 on May 9, 2006 +<ul> + <li>A bug of replace(String,ExprEditor) in javassist.expr.Expr has been fixed. + <li>Updated ProxyFactory getClassLoader to choose the javassit class loader + when the proxy superclass has a null class loader (a jdk/endorsed class) + (<a href='http://jira.jboss.com/jira/browse/JASSIST-18'>JASSIST-18</a>). + <li>Updated the throws clause of the javassist.util.proxy.MethodHandler + to be Throwable rather than Exception + (<a href='http://jira.jboss.com/jira/browse/JASSIST-16'>JASSIST-16</a>). +</ul> + +<p>- version 3.2.0.CR1 on March 18, 2006 +<ul> + <li>Annotations enhancements to javassist.bytecode.MethodInfo. + <li>Allow a ClassPool to override the "guess" at the classloader to use. +</ul> + +<p>- version 3.1 on February 23, 2006 + +<ul> + <li>getFields(), getMethods(), and getConstructors() in CtClass + were changed to return non-private memebers instead of only + public members. + <li>getEnclosingClass() in javassist.CtClass was renamed + to getEnclosingMethod(). + <li>getModifiers() was extended to return Modifier.STATIC if the class + is a static inner class. + <li>The return type of CtClass.stopPruning() was changed from void + to boolean. + <li>toMethod() in javassist.CtConstructor has been implemented. + <li>It includes new javassist.util.proxy package + similar to Enhancer of CGLIB. + <p> + <li>The subpackages of Javassist were restructured. + <ul> + <li>javassist.tool package was renamed to javassist.tools. + <li>HotSwapper was moved to javassist.util. + <li>Several subpackages were moved to javassist.tools. + <li>javassist.preproc package was elminated and the source was + moved to the sample directory. + </ul> +</ul> + +<p>- version 3.1 RC2 on September 7, 2005 + +<ul> + <li>RC2 is released mainly for an administrative reason. + <li>A few bugs have been fixed. +</ul> + +<p>- version 3.1 RC1 on August 29, 2005 + +<ul> + <li>Better annotation supports. See <code>CtClass.getAnnotations()</code> + <li>javassist.tool.HotSwapper was added. + <li>javassist.ClassPool.importPackage() was added. + <li>The compiler now accepts array initializers + (only one dimensional arrays). + <li>javassist.Dump was moved to javassist.tool.Dump. + <li>Many bugs were fixed. +</ul> + +<p>- version 3.0 on January 18, 2005 + +<ul> + <li>The compiler now supports synchronized statements and finally + clauses. + <li>You can now remove a method and a field. +</ul> + +<p>- version 3.0 RC1 on September 13, 2004. + +<ul> + <li>CtClass.toClass() has been reimplemented. The behavior has been + changed. + <li>javassist.expr.NewArray has been implemented. It enables modifying + an expression for array creation. + <li><code>.class</code> notation has been supported. The modified class + file needs javassist.runtime.DotClass at runtime. + <li>a bug in <code>CtClass.getMethods()</code> has been fixed. + <li>The compiler supports a switch statement. +</ul> + +<p>- version 3.0 beta on May 18th, 2004. + +<ul> + <li>The ClassPool framework has been redesigned. + <ul> + <li>writeFile(), write(), ... in ClassPool have been moved to CtClass. + <li>The design of javassist.Translator has been changed. + </ul> + + <li>javassist.bytecode.annotation has been added for meta tags. + <li>CtClass.makeNestedClass() has been added. + <li>The methods declared in javassist.bytecode.InnerClassesAttribute + have been renamed a bit. + <li>Now local variables were made available in the source text passed to + CtBehavior.insertBefore(), MethodCall.replace(), etc. + <li>CtClass.main(), which prints the version number, has been added. + <li>ClassPool.SimpleLoader has been public. + <li>javassist.bytecode.DeprecatedAttribute has been added. + <li>javassist.bytecode.LocalVariableAttribute has been added. + <li>CtClass.getURL() and javassist.ClassPath.find() has been added. + <li>CtBehavior.insertAt() has been added. + <li>CtClass.detach() has been added. + <li>CodeAttribute.computeMaxStack() has been added. +</ul> + +<p>- version 2.6 in August, 2003. + +<ul> + <li>The behavior of CtClass.setSuperclass() was changed. + To obtain the previous behavior, call CtClass.replaceClassName(). + <li>CtConstructor.setBody() now works for class initializers. + <li>CtNewMethod.delegator() now works for static methods. + <li>javassist.expr.Expr.indexOfBytecode() has been added. + <li>javassist.Loader has been modified so that getPackage() returns + a package object. + <li>Now, the compiler can correctly compile a try statement and an + infinite while-loop. +</ul> + +<p>- version 2.5.1 in May, 2003. +<br>Simple changes for integration with JBoss AOP +<ul> + <li>Made ClassPool.get0 protected so that subclasses of ClassPool can call it. + <li>Moved all access to the class cache (the field ClassPool.classes) to a method called getCached(String classname). This is so subclasses of ClassPool can override this behavior. +</ul> + +<p>- version 2.5 in May, 2003. +<br>From this version, Javassist is part of the JBoss project. +<ul> + <li>The license was changed from MPL to MPL/LGPL dual. + <li>ClassPool.removeClassPath() and ClassPath.close() have been added. + <li>ClassPool.makeClass(InputStream) has been added. + <li>CtClass.makeClassInitializer() has been added. + <li>javassist.expr.Expr has been changed to a public class. + <li>javassist.expr.Handler has been added. + <li>javassist.expr.MethodCall.isSuper() has been added. + <li>CtMethod.isEmpty() and CtConstructor.isEmpty() have been added. + <li>LoaderClassPath has been implemented. +</ul> + +<p>- version 2.4 in February, 2003. +<ul> + <li>The compiler included in Javassist did not correctly work with + interface methods. This bug was fixed. + <li>Now javassist.bytecode.Bytecode allows more than 255 local + variables in the same method. + <li>javassist.expr.Instanceof and Cast have been added. + <li>javassist.expr.{MethodCall,NewExpr,FieldAccess,Instanceof,Cast}.where() + have been added. They return the caller-side method surrounding the + expression. + <li>javassist.expr.{MethodCall,NewExpr,FieldAccess,Instanceof,Cast}.mayThrow() + have been added. + <li>$class has been introduced. + <li>The parameters to replaceFieldRead(), replaceFieldWrite(), + and redirectFieldAccess() in javassist.CodeConverter are changed. + <li>The compiler could not correctly handle a try-catch statement. + This bug has been fixed. +</ul> + +<p>- version 2.3 in December, 2002. +<ul> + <li>The tutorial has been revised a bit. + <li>SerialVersionUID class was donated by Bob Lee. Thanks. + <li>CtMethod.setBody() and CtConstructor.setBody() have been added. + <li>javassist.reflect.ClassMetaobject.useContextClassLoader has been added. + If true, the reflection package does not use Class.forName() but uses + a context class loader specified by the user. + <li>$sig and $type are now available. + <li>Bugs in Bytecode.write() and read() have been fixed. +</ul> + +<p>- version 2.2 in October, 2002. +<ul> + <li>The tutorial has been revised. + <li>A new package <code>javassist.expr</code> has been added. + This is replacement of classic <code>CodeConverter</code>. + <li>javassist.ConstParameter was changed into + javassist.CtMethod.ConstParameter. + <li>javassist.FieldInitializer was renamed into + javassist.CtField.Initializer. + <li>A bug in javassist.bytecode.Bytecode.addInvokeinterface() has been + fixed. + <li>In javassist.bytecode.Bytecode, addGetfield(), addGetstatic(), + addInvokespecial(), addInvokestatic(), addInvokevirtual(), + and addInvokeinterface() + have been modified to update the current statck depth. +</ul> + +<p>- version 2.1 in July, 2002. +<ul> + <li>javassist.CtMember and javassist.CtBehavior have been added. + <li>javassist.CtClass.toBytecode() has been added. + <li>javassist.CtClass.toClass() and javassist.ClassPool.writeAsClass() + has been added. + <li>javassist.ByteArrayClassPath has been added. + <li>javassist.bytecode.Mnemonic has been added. + <li>Several bugs have been fixed. +</ul> + +<p>- version 2.0 (major update) in November, 2001. +<ul> + <li>The javassist.bytecode package has been provided. It is a + lower-level API for directly modifying a class file although + the users must have detailed knowledge of the Java bytecode. + + <li>The mechanism for creating CtClass objects have been changed. + + <li>javassist.tool.Dump moves to the javassist package. +</ul> + +<p>version 1.0 in July, 2001. +<ul> + <li>javassist.reflect.Metaobject and ClassMetaobject was changed. + Now they throw the same exception that they receive from a + base-level object. +</ul> + +<p>- version 0.8 +<ul> + <li>javassist.tool.Dump was added. It is a class file viewer. + + <li>javassist.FiledInitializer.byNewArray() was added. It is for + initializing a field with an array object. + + <li>javassist.CodeConverter.redirectMethodCall() was added. + + <li>javassist.Run was added. +</ul> + +<p>- version 0.7 +<ul> + <li>javassit.Loader was largely modified. javassist.UserLoader was + deleted. Instead, Codebase was renamed to ClassPath + and UserClassPath was added. Now programmers who want to + customize Loader must write a class implementing UserClassPath + instead of UserLoader. This change is for sharing class search paths + between Loader and CtClass.CtClass(String). + + <li>CtClass.addField(), addMethod(), addConstructor(), addWrapper() were + also largely modified so that it receives CtNewMethod, CtNewConstructor, + or CtNewField. The static methods for creating these objects were + added to the API. + + <li>Constructors are now represented by CtConstructor objects. + CtConstructor is a subclass of CtMethod. + + <li>CtClass.getUserAttribute() was removed. Use CtClass.getAttribute(). + + <li>javassist.rmi.RmiLoader was added. + + <li>javassist.reflect.Metalevel._setMetaobject() was added. Now + metaobjects can be replaced at runtime. +</ul> + +<p>- version 0.6 +<ul> + <li>Javassist was modified to correctly deal with array types appearing + in signatures. + + <li>A bug crashed resulting bytecode if a class includes a private static + filed. It has been fixed. + + <li>javassist.CtNewInterface was added. + + <li>javassist.Loader.recordClass() was renamed into makeClass(). + + <li>javassist.UserLoader.loadClass() was changed to take the second + parameter. +</ul> + +<p>- version 0.5 +<ul> + <li>a bug-fix version. +</ul> + +<p>- version 0.4 +<ul> + <li>Major update again. Many classes and methods were changed. + Most of methods taking java.lang.Class have been changed to + take javassist.CtClass. +</ul> + +<p>- version 0.3 +<ul> + <li>Major update. Many classes and methods were changed. +</ul> + +<p>- version 0.2 +<ul> + <li>Jar/zip files are supported. +</ul> + +<p>-version 0.1 on April 16, 1999. +<ul> + <li>The first release. +</ul> + +<p><br> + +<h2>Bug reports etc.</h2> + +<dl> +<dt>Bug reports: +<dd>Post your reports at <a href="http://www.jboss.org/jive.jsp">Forums</a> +or directly send an email to: +<br> +<tt><a href="mailto:chiba@acm.org">chiba@acm.org</a></tt> +or +<tt><a href="mailto:chiba@is.titech.ac.jp">chiba@is.titech.ac.jp</a></tt> +<br> + +<p><dt>The home page of Javassist: +<dd>Visit <a href="http://www.javassist.org"><tt>www.javassist.org</tt></a> +and <a href="http://www.jboss.org/index.html?module=html&op=userdisplay&id=developers/projects/javassist"><tt>www.jboss.org</tt></a> + +</dl> + +<p><br> + +<a name="copyright"> +<h2>Copyright notices</h2> + +<p>Javassist, a Java-bytecode translator toolkit. +<br>Copyright (C) 1999-2010 Shigeru Chiba. All Rights Reserved. + +<p>The contents of this software, Javassist, are subject to +the Mozilla Public License Version 1.1 (the "License");<br> +you may not use this software except in compliance +with the License. You may obtain a copy of the License at +<br>http://www.mozilla.org/MPL/ + +<p>Software distributed under the License is distributed on an "AS IS" +basis, WITHOUT WARRANTY OF <br>ANY KIND, either express or implied. +See the License for the specific language governing rights and +<br>limitations under the License. + +<p>The Original Code is Javassist. + +<p>The Initial Developer of the Original Code is Shigeru Chiba. +Portions created by the Initial Developer are<br> +Copyright (C) 1999-2010 Shigeru Chiba. All Rights Reserved. +<p>Contributor(s): ______________________________________. + +<p>Alternatively, the contents of this software may be used under the +terms of the GNU Lesser General Public License Version 2.1 or later +(the "LGPL"), in which case the provisions of the LGPL are applicable +instead of those above. If you wish to allow use of your version of +this software only under the terms of the LGPL, and not to allow others to +use your version of this software under the terms of the MPL, indicate +your decision by deleting the provisions above and replace them with +the notice and other provisions required by the LGPL. If you do not +delete the provisions above, a recipient may use your version of this +software under the terms of either the MPL or the LGPL. + +<p>If you obtain this software as part of JBoss, the contents of this +software may be used under only the terms of the LGPL. To use them +under the MPL, you must obtain a separate package including only +Javassist but not the other part of JBoss. + +<p>All the contributors to the original source tree must agree to +the original license term described above. + +<p><br> + +<h2>Acknowledgments</h2> + +<p>The development of this software is sponsored in part by the PRESTO +and CREST programs of <a href="http://www.jst.go.jp/">Japan +Science and Technology Corporation</a>. + +<p>I'd like to thank Michiaki Tatsubori, Johan Cloetens, +Philip Tomlinson, Alex Villazon, Pascal Rapicault, Dan HE, Eric Tanter, +Michael Haupt, Toshiyuki Sasaki, Renaud Pawlak, Luc Bourlier, +Eric Bui, Lewis Stiller, Susumu Yamazaki, Rodrigo Teruo Tomita, +Marc Segura-Devillechaise, Jan Baudisch, Julien Blass, Yoshiki Sato, +Fabian Crabus, Bo Norregaard Jorgensen, Bob Lee, Bill Burke, +Remy Sanlaville, Muga Nishizawa, Alexey Loubyansky, Saori Oki, +Andreas Salathe, Dante Torres estrada, S. Pam, Nuno Santos, +Denis Taye, Colin Sampaleanu, Robert Bialek, Asato Shimotaki, +Howard Lewis Ship, Richard Jones, Marjan Sterjev, +Bruce McDonald, Mark Brennan, Vlad Skarzhevskyy, +Brett Randall, Tsuyoshi Murakami, Nathan Meyers, Yoshiyuki Usui +Yutaka Sunaga, Arjan van der Meer, Bruce Eckel, Guillaume Pothier, +Kumar Matcha, Andreas Salathe, Renat Zubairov, Armin Haaf, +Emmanuel Bernard +and all other contributors for their contributions. + +<p><br> + +<hr> +<a href="http://www.is.titech.ac.jp/~chiba">Shigeru Chiba</a> +(Email: <tt>chiba@acm.org</tt>) +<br>Dept. of Math. and Computing Sciences, +<a href="http://www.titech.ac.jp">Tokyo Institute of Technology</a> diff --git a/build.properties b/build.properties new file mode 100644 index 0000000..42e774e --- /dev/null +++ b/build.properties @@ -0,0 +1,4 @@ +# We only use junit, so we set the lib.dir to the junit out dir; this +# works only on full builds that include master and in unix (though +# you can change the path in your local repository). +lib.dir=../../out/host/linux-x86/framework diff --git a/build.xml b/build.xml new file mode 100644 index 0000000..9d4c329 --- /dev/null +++ b/build.xml @@ -0,0 +1,298 @@ +<?xml version="1.0"?> + +<!-- =================================================================== --> +<!-- JBoss build file --> +<!-- =================================================================== --> + +<project name="javassist" default="jar" basedir="."> + + <property name="dist-version" value="javassist-3.14.0-GA"/> + + <property environment="env"/> + <property name="target.jar" value="javassist.jar"/> + <property name="target-src.jar" value="javassist-src.jar"/> + <property name="lib.dir" value="${basedir}/lib"/> + <property name="src.dir" value="${basedir}/src/main"/> + <property name="build.dir" value="${basedir}/build"/> + <property name="build.classes.dir" value="${build.dir}/classes"/> + <property name="test.src.dir" value="${basedir}/src/test"/> + <property name="test.build.dir" value="${basedir}/build/test-classes"/> + <property name="test.reports.dir" value = "${basedir}/build/test-output"/> + + <property name="run.dir" value="${build.classes.dir}"/> + + <!-- Build classpath --> + <path id="classpath"> + <pathelement location="${build.classes.dir}"/> + </path> + + <property name="build.classpath" refid="classpath"/> + + <path id="test.compile.classpath"> + <pathelement location="${build.classes.dir}"/> + <pathelement location="${lib.dir}/junit.jar"/> + </path> + + <property name="test.compile.classpath" refid="test.compile.classpath"/> + + <path id="test.classpath"> + <pathelement location="${test.build.dir}"/> + <pathelement location="${lib.dir}/junit.jar"/> + <pathelement location="${build.classes.dir}"/> + </path> + + <property name="test.classpath" refid="test.classpath"/> + + <!-- =================================================================== --> + <!-- Prepares the build directory --> + <!-- =================================================================== --> + <target name="prepare" > + <mkdir dir="${build.dir}"/> + <mkdir dir="${build.classes.dir}"/> + <mkdir dir="${test.build.dir}"/> + <mkdir dir="${test.reports.dir}"/> + </target> + + <!-- =================================================================== --> + <!-- Compiles the source code --> + <!-- =================================================================== --> + <target name="compile" depends="prepare"> + <javac srcdir="${src.dir}" + destdir="${build.classes.dir}" + debug="on" + deprecation="on" + optimize="off" + includes="**"> + <classpath refid="classpath"/> + </javac> + </target> + + <target name="compile14" depends="prepare"> + <javac srcdir="${src.dir}" + destdir="${build.classes.dir}" + debug="on" + deprecation="on" + source="1.4" + target="1.4" + optimize="off" + includes="**"> + <classpath refid="classpath"/> + </javac> + </target> + + <target name="test-compile" depends="compile"> + <javac srcdir="${test.src.dir}" + destdir="${test.build.dir}" + debug="on" + deprecation="on" + optimize="off" + includes="**"> + <classpath refid="test.compile.classpath"/> + </javac> + </target> + + <target name="test" depends="test-compile"> + <junit fork="true" printsummary="true"> + <classpath refid="test.classpath"/> + <formatter type="plain"/> + <formatter type="xml"/> + <batchtest todir="${test.reports.dir}"> + <fileset dir="${test.build.dir}"> + <include name="**/*Test.*"/> + </fileset> + </batchtest> + </junit> + </target> + + <target name="sample" depends="compile"> + <javac srcdir="${basedir}" + destdir="${build.classes.dir}" + debug="on" + deprecation="on" + optimize="off" + includes="sample/**" + excludes="sample/hotswap/**,sample/evolve/sample/**"> + <classpath refid="classpath"/> + </javac> + + <copy file="sample/vector/Test.j" + todir="${build.classes.dir}/sample/vector"/> + + <javac srcdir="${basedir}/sample/evolve" + destdir="${build.classes.dir}/sample/evolve/" + debug="on" + deprecation="on" + optimize="off" + includes="sample/**"> + <classpath refid="classpath"/> + </javac> + <copy todir="${build.classes.dir}/sample/evolve"> + <fileset dir="sample/evolve"/> + </copy> + <copy file="${build.classes.dir}/sample/evolve/WebPage.class" + tofile="${build.classes.dir}/sample/evolve/WebPage.class.0"/> + <copy file="${build.classes.dir}/sample/evolve/sample/evolve/WebPage.class" + tofile="${build.classes.dir}/sample/evolve/WebPage.class.1"/> + + <javac srcdir="${basedir}/sample/hotswap" + destdir="${build.classes.dir}" + debug="on" + deprecation="on" + optimize="off" + includes="*"> + <classpath refid="classpath"/> + </javac> + <mkdir dir="${build.classes.dir}/logging"/> + <javac srcdir="${basedir}/sample/hotswap/logging" + destdir="${build.classes.dir}/logging" + debug="on" + deprecation="on" + optimize="off" + includes="*"> + <classpath refid="classpath"/> + </javac> + <echo>To run the sample programs without ant, change the current directory +to ${build.classes.dir}.</echo> + </target> + + <target name="jar" depends="compile14"> + <jar jarfile="${target.jar}" manifest="${src.dir}/META-INF/MANIFEST.MF"> + <fileset dir="${build.classes.dir}"> + <include name="**/*.class"/> + </fileset> + </jar> + <jar jarfile="${target-src.jar}" manifest="${src.dir}/META-INF/MANIFEST.MF"> + <fileset dir="${src.dir}"> + <include name="javassist/**"/> + </fileset> + </jar> + </target> + + <target name="javadocs"> + <mkdir dir="html"/> + <javadoc + Locale="en_US" + packagenames="javassist.*" + excludepackagenames="javassist.compiler.*,javassist.convert.*,javassist.scopedpool.*,javassist.bytecode.stackmap.*" + sourcepath="src/main" + defaultexcludes="yes" + destdir="html" + author="true" + version="true" + use="true" + public="true" + nohelp="true" + windowtitle="Javassist API"> + <doctitle><![CDATA[<h1>Javassist</h1>]]></doctitle> + <bottom><![CDATA[<i>Javassist, a Java-bytecode translator toolkit.<br> +Copyright (C) 1999-2010 Shigeru Chiba. All Rights Reserved.</i>]]></bottom> + </javadoc> + </target> + + <target name="dist" depends="jar,javadocs"> + <delete file="${dist-version}.zip"/> + <zip zipfile="${dist-version}.zip"> + <zipfileset dir="${basedir}" prefix="${dist-version}"> + <include name="html/**"/> + <include name="sample/**"/> + <include name="src/main/**"/> + <include name="tutorial/**"/> + <include name="*.html"/> + <include name="*.xml"/> + <include name="${target.jar}"/> + </zipfileset> + </zip> + </target> + + <target name="clean"> + <delete dir="build"/> + <delete dir="html"/> + <delete file="${target.jar}"/> + <delete file="${dist-version}.zip"/> + </target> + + <!-- =================================================================== --> + <!-- Run samples --> + <!-- =================================================================== --> + + <target name = "sample-all" + depends="sample-test,sample-reflect,sample-duplicate,sample-vector"> + <echo>** please run sample-rmi, sample-evolve, and</echo> + <echo> sample-hotswap (or -hotswap5) separately **</echo> + </target> + + <target name = "sample-test" depends="sample" > + <java fork="true" dir="${run.dir}" classname="sample.Test"> + <classpath refid="classpath"/> + </java> + </target> + + <target name = "sample-reflect" depends="sample" > + <java fork="true" dir="${run.dir}" classname="javassist.tools.reflect.Loader"> + <classpath refid="classpath"/> + <arg line="sample.reflect.Main Joe" /> + </java> + </target> + + <target name = "sample-duplicate" depends="sample" > + <echo>run sample.duplicate.Viewer without reflection</echo> + <java fork="true" dir="${run.dir}" classname="sample.duplicate.Viewer"> + <classpath refid="classpath"/> + </java> + <echo>run sample.duplicate.Viewer with reflection</echo> + <java fork="true" dir="${run.dir}" classname="sample.duplicate.Main"> + <classpath refid="classpath"/> + </java> + </target> + + <target name = "sample-vector" depends="sample" > + <echo>sample.preproc.Compiler sample/vector/Test.j</echo> + <java fork="true" dir="${run.dir}" classname="sample.preproc.Compiler"> + <classpath refid="classpath"/> + <arg line="sample/vector/Test.j"/> + </java> + <echo>javac sample/vector/Test.java</echo> + <javac srcdir="${build.classes.dir}" + destdir="${build.classes.dir}" + includes="sample/vector/Test.java"> + <classpath refid="classpath"/> + </javac> + <java fork="true" dir="${run.dir}" classname="sample.vector.Test" /> + </target> + + <target name = "sample-rmi" depends="sample" > + <echo>** Please open sample/rmi/webdemo.html with your browser **</echo> + <java fork="true" dir="${run.dir}" classname="sample.rmi.Counter"> + <classpath refid="classpath"/> + <arg value="5001" /> + </java> + </target> + + <target name = "sample-evolve" depends="sample" > + <echo>** Please open http://localhost:5003/demo.html with your browser **</echo> + <java fork="true" dir="${run.dir}" classname="sample.evolve.DemoLoader"> + <classpath refid="classpath"/> + <arg value="5003" /> + </java> + </target> + + <!-- for JDK 1.4 --> + <target name = "sample-hotswap" depends="sample"> + <echo>** JAVA_HOME/lib/tools.jar must be included in CLASS_PATH</echo> + <echo>** for JDK 1.4</echo> + <java fork="true" dir="${run.dir}" classname="Test"> + <jvmarg line="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8000" /> + <classpath refid="classpath"/> + </java> + </target> + + <!-- for Java 5 --> + <target name = "sample-hotswap5" depends="sample"> + <echo>** JAVA_HOME/lib/tools.jar must be included in CLASS_PATH</echo> + <echo>** for JDK 1.5 or later</echo> + <java fork="true" dir="${run.dir}" classname="Test"> + <jvmarg line="-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8000" /> + <classpath refid="classpath"/> + </java> + </target> +</project> @@ -0,0 +1,262 @@ +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + <groupId>org.javassist</groupId> + <artifactId>javassist</artifactId> + <packaging>jar</packaging> + <description>Javassist (JAVA programming ASSISTant) makes Java bytecode manipulation + simple. It is a class library for editing bytecodes in Java. + </description> + <version>3.14.0-GA</version> + <name>Javassist</name> + <url>http://www.javassist.org/</url> + + <issueManagement> + <system>JIRA</system> + <url>https://jira.jboss.org/jira/browse/JASSIST/</url> + </issueManagement> + <licenses> + <!-- this is the license under which javassist is usually distributed + --> + <license> + <name>MPL 1.1</name> + <url>http://www.mozilla.org/MPL/MPL-1.1.html</url> + </license> + <!-- this is the license under which javassist is distributed when + it is bundled with JBoss + --> + <license> + <name>LGPL 2.1</name> + <url>http://www.gnu.org/licenses/lgpl-2.1.html</url> + </license> + </licenses> + + <scm> + <connection>scm:svn:http://anonsvn.jboss.org/repos/javassist/</connection> + <developerConnection>scm:svn:https://svn.jboss.org/repos/javassist/</developerConnection> + <url>http://fisheye.jboss.org/browse/javassist/</url> + </scm> + + <developers> + <developer> + <id>chiba</id> + <name>Shigeru Chiba</name> + <email>chiba@acm.org</email> + <organization>Tokyo Institute of Technology</organization> + <organizationUrl>http://www.javassist.org/</organizationUrl> + <roles> + <role>project lead</role> + </roles> + <timezone>8</timezone> + </developer> + + <developer> + <id>adinn</id> + <name>Andrew Dinn</name> + <email>adinn@redhat.com</email> + <organization>JBoss</organization> + <organizationUrl>http://www.jboss.org/</organizationUrl> + <roles> + <role>contributing developer</role> + </roles> + <timezone>0</timezone> + </developer> + + <developer> + <id>kabir.khan@jboss.com</id> + <name>Kabir Khan</name> + <email>kabir.khan@jboss.com</email> + <organization>JBoss</organization> + <organizationUrl>http://www.jboss.org/</organizationUrl> + <roles> + <role>contributing developer</role> + </roles> + <timezone>0</timezone> + </developer> + </developers> + + <distributionManagement> + <!-- + You need entries in your .m2/settings.xml like this: + <servers> + <server> + <id>jboss-releases-repository</id> + <username>your_jboss.org_username</username> + <password>password</password> + </server> + <server> + <id>jboss-snapshots-repository</id> + <username>your_jboss.org_username</username> + <password>password</password> + </server> + </servers> + + To deploy a snapshot, you need to run + + mvn deploy -Dversion=3.x.y-SNAPSHOT + + To deploy a release you need to change the version to 3.x.y.GA and run + + mvn deploy + --> + <repository> + <id>jboss-releases-repository</id> + <name>JBoss Releases Repository</name> + <url>https://repository.jboss.org/nexus/service/local/staging/deploy/maven2/</url> + </repository> + <snapshotRepository> + <id>jboss-snapshots-repository</id> + <name>JBoss Snapshots Repository</name> + <url>https://repository.jboss.org/nexus/content/repositories/snapshots/</url> + </snapshotRepository> + </distributionManagement> + <build> + <sourceDirectory>src/main/</sourceDirectory> + <testSourceDirectory>src/test/</testSourceDirectory> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + <configuration> + <source>1.4</source> + <target>1.4</target> + </configuration> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-jar-plugin</artifactId> + <configuration> + <archive> + <manifestFile>${project.build.sourceDirectory}/META-INF/MANIFEST.MF</manifestFile> + </archive> + </configuration> + </plugin> + <plugin> + <artifactId>maven-source-plugin</artifactId> + <version>2.0.3</version> + <executions> + <execution> + <id>attach-sources</id> + <goals> + <goal>jar</goal> + </goals> + </execution> + </executions> + <inherited>true</inherited> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-javadoc-plugin</artifactId> + <version>2.7</version> + <configuration> + <attach>true</attach> + </configuration> + </plugin> + </plugins> + </build> + <profiles> + <!-- profile for releasing to sonatype repo + exercise with mvn -PcentralRelease + --> + <profile> + <id>centralRelease</id> + <!-- obviously we need to use the Sonatype staging repo for upload --> + <distributionManagement> + <repository> + <id>sonatype-releases-repository</id> + <name>Sonatype Releases Repository</name> + <url>https://oss.sonatype.org/service/local/staging/deploy/maven2</url> + </repository> + </distributionManagement> + <!-- we need to be able to sign the jars we install --> + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-gpg-plugin</artifactId> + <configuration> + <passphrase>${gpg.passphrase}</passphrase> + <useAgent>${gpg.useAgent}</useAgent> + </configuration> + <executions> + <execution> + <id>sign-artifacts</id> + <phase>verify</phase> + <goals> + <goal>sign</goal> + </goals> + </execution> + </executions> + </plugin> + </plugins> + </build> + </profile> + <!-- profiles to add tools jar containing com.sun.jdi code + needed by sample code + --> + <profile> + <id>jdk14</id> + <activation> + <jdk>1.4</jdk> + <property> + <name>!no.tools</name> + </property> + </activation> + <dependencies> + <dependency> + <groupId>com.sun</groupId> + <artifactId>tools</artifactId> + <version>1.4</version> + <scope>system</scope> + <optional>true</optional> + <systemPath>${java.home}/../lib/tools.jar</systemPath> + </dependency> + </dependencies> + </profile> + <profile> + <id>jdk15</id> + <activation> + <jdk>1.5</jdk> + <property> + <name>!no.tools</name> + </property> + </activation> + <dependencies> + <dependency> + <groupId>com.sun</groupId> + <artifactId>tools</artifactId> + <version>1.5</version> + <scope>system</scope> + <optional>true</optional> + <systemPath>${java.home}/../lib/tools.jar</systemPath> + </dependency> + </dependencies> + </profile> + <profile> + <id>jdk16</id> + <activation> + <jdk>1.6</jdk> + <property> + <name>!no.tools</name> + </property> + </activation> + <dependencies> + <dependency> + <groupId>com.sun</groupId> + <artifactId>tools</artifactId> + <version>1.6</version> + <scope>system</scope> + <optional>true</optional> + <systemPath>${java.home}/../lib/tools.jar</systemPath> + </dependency> + </dependencies> + </profile> + </profiles> + <dependencies> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <version>3.8.1</version> + <scope>test</scope> + </dependency> + </dependencies> +</project> diff --git a/regenerate_from_source.sh b/regenerate_from_source.sh new file mode 100644 index 0000000..2f744e1 --- /dev/null +++ b/regenerate_from_source.sh @@ -0,0 +1,14 @@ +#!/bin/bash +# +# Copyright (C) 2011 The Android Open Source Project. +# This script imports the src, test, etc. from javassist. It DOESN'T commit +# to git giving you a chance to review the changes. Remember that changes in +# bin are normally ignored by git, but we need to force them this case. +# +# This script doesn't take any parameter. + +svn export --force http://anonsvn.jboss.org/repos/javassist/trunk . +rm lib/junit.jar +# I don't check if there is something on lib on purpose; that way, if +# anything new is added, we will get a visible error. +rmdir lib diff --git a/sample/Test.java b/sample/Test.java new file mode 100644 index 0000000..1e2b49c --- /dev/null +++ b/sample/Test.java @@ -0,0 +1,44 @@ +package sample; + +import javassist.*; + +/* + A very simple sample program + + This program overwrites sample/Test.class (the class file of this + class itself) for adding a method g(). If the method g() is not + defined in class Test, then this program adds a copy of + f() to the class Test with name g(). Otherwise, this program does + not modify sample/Test.class at all. + + To see the modified class definition, execute: + + % javap sample.Test + + after running this program. +*/ +public class Test { + public int f(int i) { + return i + 1; + } + + public static void main(String[] args) throws Exception { + ClassPool pool = ClassPool.getDefault(); + + CtClass cc = pool.get("sample.Test"); + try { + cc.getDeclaredMethod("g"); + System.out.println("g() is already defined in sample.Test."); + } + catch (NotFoundException e) { + /* getDeclaredMethod() throws an exception if g() + * is not defined in sample.Test. + */ + CtMethod fMethod = cc.getDeclaredMethod("f"); + CtMethod gMethod = CtNewMethod.copy(fMethod, "g", cc, null); + cc.addMethod(gMethod); + cc.writeFile(); // update the class file + System.out.println("g() was added."); + } + } +} diff --git a/sample/duplicate/Ball.java b/sample/duplicate/Ball.java new file mode 100644 index 0000000..21d6e1c --- /dev/null +++ b/sample/duplicate/Ball.java @@ -0,0 +1,44 @@ +package sample.duplicate;
+
+import java.awt.Graphics;
+import java.awt.Color;
+
+public class Ball {
+ private int x, y;
+ private Color color;
+ private int radius = 30;
+ private boolean isBackup = false;
+
+ public Ball(int x, int y) {
+ move(x, y);
+ changeColor(Color.orange);
+ }
+
+ // This constructor is for a backup object.
+ public Ball(Ball b) {
+ isBackup = true;
+ }
+
+ // Adjust the position so that the backup object is visible.
+ private void adjust() {
+ if (isBackup) {
+ this.x += 50;
+ this.y += 50;
+ }
+ }
+
+ public void paint(Graphics g) {
+ g.setColor(color);
+ g.fillOval(x, y, radius, radius);
+ }
+
+ public void move(int x, int y) {
+ this.x = x;
+ this.y = y;
+ adjust();
+ }
+
+ public void changeColor(Color color) {
+ this.color = color;
+ }
+}
diff --git a/sample/duplicate/DuplicatedObject.java b/sample/duplicate/DuplicatedObject.java new file mode 100644 index 0000000..7161493 --- /dev/null +++ b/sample/duplicate/DuplicatedObject.java @@ -0,0 +1,38 @@ +package sample.duplicate;
+
+import javassist.tools.reflect.*;
+
+public class DuplicatedObject extends Metaobject {
+ private DuplicatedObject backup;
+
+ // if a base-level object is created, this metaobject creates
+ // a copy of the base-level object.
+
+ public DuplicatedObject(Object self, Object[] args) {
+ super(self, args);
+ ClassMetaobject clazz = getClassMetaobject();
+ if (clazz.isInstance(args[0]))
+ backup = null; // self is a backup object.
+ else {
+ Object[] args2 = new Object[1];
+ args2[0] = self;
+ try {
+ Metalevel m = (Metalevel)clazz.newInstance(args2);
+ backup = (DuplicatedObject)m._getMetaobject();
+ }
+ catch (CannotCreateException e) {
+ backup = null;
+ }
+ }
+ }
+
+ public Object trapMethodcall(int identifier, Object[] args)
+ throws Throwable
+ {
+ Object obj = super.trapMethodcall(identifier, args);
+ if (backup != null)
+ backup.trapMethodcall(identifier, args);
+
+ return obj;
+ }
+}
diff --git a/sample/duplicate/Main.java b/sample/duplicate/Main.java new file mode 100644 index 0000000..064f13c --- /dev/null +++ b/sample/duplicate/Main.java @@ -0,0 +1,44 @@ +package sample.duplicate;
+
+/*
+ Runtime metaobject (JDK 1.2 or later only).
+
+ With the javassist.tools.reflect package, the users can attach a metaobject
+ to an object. The metaobject can control the behavior of the object.
+ For example, you can implement fault tolerancy with this ability. One
+ of the implementation techniques of fault tolernacy is to make a copy
+ of every object containing important data and maintain it as a backup.
+ If the machine running the object becomes down, the backup object on a
+ different machine is used to continue the execution.
+
+ To make the copy of the object a real backup, all the method calls to
+ the object must be also sent to that copy. The metaobject is needed
+ for this duplication of the method calls. It traps every method call
+ and invoke the same method on the copy of the object so that the
+ internal state of the copy is kept equivalent to that of the original
+ object.
+
+ First, run sample.duplicate.Viewer without a metaobject.
+
+ % java sample.duplicate.Viewer
+
+ This program shows a ball in a window.
+
+ Then, run the same program with a metaobject, which is an instance
+ of sample.duplicate.DuplicatedObject.
+
+ % java sample.duplicate.Main
+
+ You would see two balls in a window. This is because
+ sample.duplicate.Viewer is loaded by javassist.tools.reflect.Loader so that
+ a metaobject would be attached.
+*/
+public class Main {
+ public static void main(String[] args) throws Throwable {
+ javassist.tools.reflect.Loader cl = new javassist.tools.reflect.Loader();
+ cl.makeReflective("sample.duplicate.Ball",
+ "sample.duplicate.DuplicatedObject",
+ "javassist.tools.reflect.ClassMetaobject");
+ cl.run("sample.duplicate.Viewer", args);
+ }
+}
diff --git a/sample/duplicate/Viewer.java b/sample/duplicate/Viewer.java new file mode 100644 index 0000000..aec13f6 --- /dev/null +++ b/sample/duplicate/Viewer.java @@ -0,0 +1,78 @@ +package sample.duplicate;
+
+import java.applet.*;
+import java.awt.*;
+import java.awt.event.*;
+
+public class Viewer extends Applet
+ implements MouseListener, ActionListener, WindowListener
+{
+ private static final Color[] colorList = {
+ Color.orange, Color.pink, Color.green, Color.blue };
+
+ private Ball ball;
+ private int colorNo;
+
+ public void init() {
+ colorNo = 0;
+ Button b = new Button("change");
+ b.addActionListener(this);
+ add(b);
+
+ addMouseListener(this);
+ }
+
+ public void start() {
+ ball = new Ball(50, 50);
+ ball.changeColor(colorList[0]);
+ }
+
+ public void paint(Graphics g) {
+ ball.paint(g);
+ }
+
+ public void mouseClicked(MouseEvent ev) {
+ ball.move(ev.getX(), ev.getY());
+ repaint();
+ }
+
+ public void mouseEntered(MouseEvent ev) {}
+
+ public void mouseExited(MouseEvent ev) {}
+
+ public void mousePressed(MouseEvent ev) {}
+
+ public void mouseReleased(MouseEvent ev) {}
+
+ public void actionPerformed(ActionEvent e) {
+ ball.changeColor(colorList[++colorNo % colorList.length]);
+ repaint();
+ }
+
+ public void windowOpened(WindowEvent e) {}
+
+ public void windowClosing(WindowEvent e) {
+ System.exit(0);
+ }
+
+ public void windowClosed(WindowEvent e) {}
+
+ public void windowIconified(WindowEvent e) {}
+
+ public void windowDeiconified(WindowEvent e) {}
+
+ public void windowActivated(WindowEvent e) {}
+
+ public void windowDeactivated(WindowEvent e) {}
+
+ public static void main(String[] args) {
+ Frame f = new Frame("Viewer");
+ Viewer view = new Viewer();
+ f.addWindowListener(view);
+ f.add(view);
+ f.setSize(300, 300);
+ view.init();
+ view.start();
+ f.setVisible(true);
+ }
+}
diff --git a/sample/evolve/CannotCreateException.java b/sample/evolve/CannotCreateException.java new file mode 100644 index 0000000..828afb0 --- /dev/null +++ b/sample/evolve/CannotCreateException.java @@ -0,0 +1,14 @@ +package sample.evolve;
+
+/**
+ * Signals that VersionManager.newInstance() fails.
+ */
+public class CannotCreateException extends RuntimeException {
+ public CannotCreateException(String s) {
+ super(s);
+ }
+
+ public CannotCreateException(Exception e) {
+ super("by " + e.toString());
+ }
+}
diff --git a/sample/evolve/CannotUpdateException.java b/sample/evolve/CannotUpdateException.java new file mode 100644 index 0000000..bd28bba --- /dev/null +++ b/sample/evolve/CannotUpdateException.java @@ -0,0 +1,14 @@ +package sample.evolve;
+
+/**
+ * Signals that VersionManager.update() fails.
+ */
+public class CannotUpdateException extends Exception {
+ public CannotUpdateException(String msg) {
+ super(msg);
+ }
+
+ public CannotUpdateException(Exception e) {
+ super("by " + e.toString());
+ }
+}
diff --git a/sample/evolve/DemoLoader.java b/sample/evolve/DemoLoader.java new file mode 100644 index 0000000..7c770bd --- /dev/null +++ b/sample/evolve/DemoLoader.java @@ -0,0 +1,38 @@ +package sample.evolve; + +import javassist.*; + +/** + * DemoLoader is a class loader for running a program including + * an updatable class. This simple loader allows only a single + * class to be updatable. (Extending it for supporting multiple + * updatable classes is easy.) + * + * To run, type as follows: + * + * % java sample.evolve.DemoLoader <port number> + * + * Then DemoLoader launches sample.evolve.DemoServer with <port number>. + * It also translates sample.evolve.WebPage, which sample.evolve.DemoServer + * uses, so that it is an updable class. + * + * Note: JDK 1.2 or later only. + */ +public class DemoLoader { + private static final int initialVersion = 0; + private String updatableClassName = null; + private CtClass updatableClass = null; + + /* Creates a <code>DemoLoader</code> for making class WebPage + * updatable. Then it runs main() in sample.evolve.DemoServer. + */ + public static void main(String[] args) throws Throwable { + Evolution translator = new Evolution(); + ClassPool cp = ClassPool.getDefault(); + Loader cl = new Loader(); + cl.addTranslator(cp, translator); + + translator.makeUpdatable("sample.evolve.WebPage"); + cl.run("sample.evolve.DemoServer", args); + } +} diff --git a/sample/evolve/DemoServer.java b/sample/evolve/DemoServer.java new file mode 100644 index 0000000..b334bcc --- /dev/null +++ b/sample/evolve/DemoServer.java @@ -0,0 +1,102 @@ +package sample.evolve;
+
+import javassist.tools.web.*;
+import java.io.*;
+
+/**
+ * A web server for demonstrating class evolution. It must be
+ * run with a DemoLoader.
+ *
+ * If a html file /java.html is requested, this web server calls
+ * WebPage.show() for constructing the contents of that html file
+ * So if a DemoLoader changes the definition of WebPage, then
+ * the image of /java.html is also changed.
+ * Note that WebPage is not an applet. It is rather
+ * similar to a CGI script or a servlet. The web server never
+ * sends the class file of WebPage to web browsers.
+ *
+ * Furthermore, if a html file /update.html is requested, this web
+ * server overwrites WebPage.class (class file) and calls update()
+ * in VersionManager so that WebPage.class is loaded into the JVM
+ * again. The new contents of WebPage.class are copied from
+ * either sample/evolve/WebPage.class
+ * or sample/evolve/sample/evolve/WebPage.class.
+ */
+public class DemoServer extends Webserver {
+
+ public static void main(String[] args) throws IOException
+ {
+ if (args.length == 1) {
+ DemoServer web = new DemoServer(Integer.parseInt(args[0]));
+ web.run();
+ }
+ else
+ System.err.println(
+ "Usage: java sample.evolve.DemoServer <port number>");
+ }
+
+ public DemoServer(int port) throws IOException {
+ super(port);
+ htmlfileBase = "sample/evolve/";
+ }
+
+ private static final String ver0 = "sample/evolve/WebPage.class.0";
+ private static final String ver1 = "sample/evolve/WebPage.class.1";
+ private String currentVersion = ver0;
+
+ public void doReply(InputStream in, OutputStream out, String cmd)
+ throws IOException, BadHttpRequest
+ {
+ if (cmd.startsWith("GET /java.html ")) {
+ runJava(out);
+ return;
+ }
+ else if (cmd.startsWith("GET /update.html ")) {
+ try {
+ if (currentVersion == ver0)
+ currentVersion = ver1;
+ else
+ currentVersion = ver0;
+
+ updateClassfile(currentVersion);
+ VersionManager.update("sample.evolve.WebPage");
+ }
+ catch (CannotUpdateException e) {
+ logging(e.toString());
+ }
+ catch (FileNotFoundException e) {
+ logging(e.toString());
+ }
+ }
+
+ super.doReply(in, out, cmd);
+ }
+
+ private void runJava(OutputStream outs) throws IOException {
+ OutputStreamWriter out = new OutputStreamWriter(outs);
+ out.write("HTTP/1.0 200 OK\r\n\r\n");
+ WebPage page = new WebPage();
+ page.show(out);
+ out.close();
+ }
+
+ /* updateClassfile() copies the specified file to WebPage.class.
+ */
+ private void updateClassfile(String filename)
+ throws IOException, FileNotFoundException
+ {
+ byte[] buf = new byte[1024];
+
+ FileInputStream fin
+ = new FileInputStream(filename);
+ FileOutputStream fout
+ = new FileOutputStream("sample/evolve/WebPage.class");
+ for (;;) {
+ int len = fin.read(buf);
+ if (len >= 0)
+ fout.write(buf, 0, len);
+ else
+ break;
+ }
+ }
+}
diff --git a/sample/evolve/Evolution.java b/sample/evolve/Evolution.java new file mode 100644 index 0000000..e804ff4 --- /dev/null +++ b/sample/evolve/Evolution.java @@ -0,0 +1,189 @@ +package sample.evolve;
+
+import javassist.*;
+
+/**
+ * Evolution provides a set of methods for instrumenting bytecodes.
+ *
+ * For class evolution, updatable class A is renamed to B. Then an abstract
+ * class named A is produced as the super class of B. If the original class A
+ * has a public method m(), then the abstract class A has an abstract method
+ * m().
+ *
+ * abstract class A abstract m() _makeInstance() | class A --------> class B m()
+ * m()
+ *
+ * Also, all the other classes are translated so that "new A(i)" in the methods
+ * is replaced with "_makeInstance(i)". This makes it possible to change the
+ * behavior of the instantiation of the class A.
+ */
+public class Evolution implements Translator {
+ public final static String handlerMethod = "_makeInstance";
+
+ public final static String latestVersionField = VersionManager.latestVersionField;
+
+ public final static String versionManagerMethod = "initialVersion";
+
+ private static CtMethod trapMethod;
+
+ private static final int initialVersion = 0;
+
+ private ClassPool pool;
+
+ private String updatableClassName = null;
+
+ private CtClass updatableClass = null;
+
+ public void start(ClassPool _pool) throws NotFoundException {
+ pool = _pool;
+
+ // Get the definition of Sample.make() and store it into trapMethod
+ // for later use.
+ trapMethod = _pool.getMethod("sample.evolve.Sample", "make");
+ }
+
+ public void onLoad(ClassPool _pool, String classname)
+ throws NotFoundException, CannotCompileException {
+ onLoadUpdatable(classname);
+
+ /*
+ * Replaces all the occurrences of the new operator with a call to
+ * _makeInstance().
+ */
+ CtClass clazz = _pool.get(classname);
+ CtClass absClass = updatableClass;
+ CodeConverter converter = new CodeConverter();
+ converter.replaceNew(absClass, absClass, handlerMethod);
+ clazz.instrument(converter);
+ }
+
+ private void onLoadUpdatable(String classname) throws NotFoundException,
+ CannotCompileException {
+ // if the class is a concrete class,
+ // classname is <updatableClassName>$$<version>.
+
+ int i = classname.lastIndexOf("$$");
+ if (i <= 0)
+ return;
+
+ String orgname = classname.substring(0, i);
+ if (!orgname.equals(updatableClassName))
+ return;
+
+ int version;
+ try {
+ version = Integer.parseInt(classname.substring(i + 2));
+ }
+ catch (NumberFormatException e) {
+ throw new NotFoundException(classname, e);
+ }
+
+ CtClass clazz = pool.getAndRename(orgname, classname);
+ makeConcreteClass(clazz, updatableClass, version);
+ }
+
+ /*
+ * Register an updatable class.
+ */
+ public void makeUpdatable(String classname) throws NotFoundException,
+ CannotCompileException {
+ if (pool == null)
+ throw new RuntimeException(
+ "Evolution has not been linked to ClassPool.");
+
+ CtClass c = pool.get(classname);
+ updatableClassName = classname;
+ updatableClass = makeAbstractClass(c);
+ }
+
+ /**
+ * Produces an abstract class.
+ */
+ protected CtClass makeAbstractClass(CtClass clazz)
+ throws CannotCompileException, NotFoundException {
+ int i;
+
+ CtClass absClass = pool.makeClass(clazz.getName());
+ absClass.setModifiers(Modifier.PUBLIC | Modifier.ABSTRACT);
+ absClass.setSuperclass(clazz.getSuperclass());
+ absClass.setInterfaces(clazz.getInterfaces());
+
+ // absClass.inheritAllConstructors();
+
+ CtField fld = new CtField(pool.get("java.lang.Class"),
+ latestVersionField, absClass);
+ fld.setModifiers(Modifier.PUBLIC | Modifier.STATIC);
+
+ CtField.Initializer finit = CtField.Initializer.byCall(pool
+ .get("sample.evolve.VersionManager"), versionManagerMethod,
+ new String[] { clazz.getName() });
+ absClass.addField(fld, finit);
+
+ CtField[] fs = clazz.getDeclaredFields();
+ for (i = 0; i < fs.length; ++i) {
+ CtField f = fs[i];
+ if (Modifier.isPublic(f.getModifiers()))
+ absClass.addField(new CtField(f.getType(), f.getName(),
+ absClass));
+ }
+
+ CtConstructor[] cs = clazz.getDeclaredConstructors();
+ for (i = 0; i < cs.length; ++i) {
+ CtConstructor c = cs[i];
+ int mod = c.getModifiers();
+ if (Modifier.isPublic(mod)) {
+ CtMethod wm = CtNewMethod.wrapped(absClass, handlerMethod, c
+ .getParameterTypes(), c.getExceptionTypes(),
+ trapMethod, null, absClass);
+ wm.setModifiers(Modifier.PUBLIC | Modifier.STATIC);
+ absClass.addMethod(wm);
+ }
+ }
+
+ CtMethod[] ms = clazz.getDeclaredMethods();
+ for (i = 0; i < ms.length; ++i) {
+ CtMethod m = ms[i];
+ int mod = m.getModifiers();
+ if (Modifier.isPublic(mod))
+ if (Modifier.isStatic(mod))
+ throw new CannotCompileException(
+ "static methods are not supported.");
+ else {
+ CtMethod m2 = CtNewMethod.abstractMethod(m.getReturnType(),
+ m.getName(), m.getParameterTypes(), m
+ .getExceptionTypes(), absClass);
+ absClass.addMethod(m2);
+ }
+ }
+
+ return absClass;
+ }
+
+ /**
+ * Modifies the given class file so that it is a subclass of the abstract
+ * class produced by makeAbstractClass().
+ *
+ * Note: the naming convention must be consistent with
+ * VersionManager.update().
+ */
+ protected void makeConcreteClass(CtClass clazz, CtClass abstractClass,
+ int version) throws CannotCompileException, NotFoundException {
+ int i;
+ clazz.setSuperclass(abstractClass);
+ CodeConverter converter = new CodeConverter();
+ CtField[] fs = clazz.getDeclaredFields();
+ for (i = 0; i < fs.length; ++i) {
+ CtField f = fs[i];
+ if (Modifier.isPublic(f.getModifiers()))
+ converter.redirectFieldAccess(f, abstractClass, f.getName());
+ }
+
+ CtConstructor[] cs = clazz.getDeclaredConstructors();
+ for (i = 0; i < cs.length; ++i)
+ cs[i].instrument(converter);
+
+ CtMethod[] ms = clazz.getDeclaredMethods();
+ for (i = 0; i < ms.length; ++i)
+ ms[i].instrument(converter);
+ }
+}
diff --git a/sample/evolve/Sample.java b/sample/evolve/Sample.java new file mode 100644 index 0000000..c147c96 --- /dev/null +++ b/sample/evolve/Sample.java @@ -0,0 +1,12 @@ +package sample.evolve;
+
+/**
+ * This is a sample class used by Transformer.
+ */
+public class Sample {
+ public static Class _version;
+
+ public static Object make(Object[] args) {
+ return VersionManager.make(_version, args);
+ }
+}
diff --git a/sample/evolve/VersionManager.java b/sample/evolve/VersionManager.java new file mode 100644 index 0000000..efecee1 --- /dev/null +++ b/sample/evolve/VersionManager.java @@ -0,0 +1,90 @@ +package sample.evolve;
+
+import java.util.Hashtable;
+import java.lang.reflect.*;
+
+/**
+ * Runtime system for class evolution
+ */
+public class VersionManager {
+ private static Hashtable versionNo = new Hashtable();
+
+ public final static String latestVersionField = "_version";
+
+ /**
+ * For updating the definition of class my.X, say:
+ *
+ * VersionManager.update("my.X");
+ */
+ public static void update(String qualifiedClassname)
+ throws CannotUpdateException {
+ try {
+ Class c = getUpdatedClass(qualifiedClassname);
+ Field f = c.getField(latestVersionField);
+ f.set(null, c);
+ }
+ catch (ClassNotFoundException e) {
+ throw new CannotUpdateException("cannot update class: "
+ + qualifiedClassname);
+ }
+ catch (Exception e) {
+ throw new CannotUpdateException(e);
+ }
+ }
+
+ private static Class getUpdatedClass(String qualifiedClassname)
+ throws ClassNotFoundException {
+ int version;
+ Object found = versionNo.get(qualifiedClassname);
+ if (found == null)
+ version = 0;
+ else
+ version = ((Integer)found).intValue() + 1;
+
+ Class c = Class.forName(qualifiedClassname + "$$" + version);
+ versionNo.put(qualifiedClassname, new Integer(version));
+ return c;
+ }
+
+ /*
+ * initiaVersion() is used to initialize the _version field of the updatable
+ * classes.
+ */
+ public static Class initialVersion(String[] params) {
+ try {
+ return getUpdatedClass(params[0]);
+ }
+ catch (ClassNotFoundException e) {
+ throw new RuntimeException("cannot initialize " + params[0]);
+ }
+ }
+
+ /**
+ * make() performs the object creation of the updatable classes. The
+ * expression "new <updatable class>" is replaced with a call to this
+ * method.
+ */
+ public static Object make(Class clazz, Object[] args) {
+ Constructor[] constructors = clazz.getConstructors();
+ int n = constructors.length;
+ for (int i = 0; i < n; ++i) {
+ try {
+ return constructors[i].newInstance(args);
+ }
+ catch (IllegalArgumentException e) {
+ // try again
+ }
+ catch (InstantiationException e) {
+ throw new CannotCreateException(e);
+ }
+ catch (IllegalAccessException e) {
+ throw new CannotCreateException(e);
+ }
+ catch (InvocationTargetException e) {
+ throw new CannotCreateException(e);
+ }
+ }
+
+ throw new CannotCreateException("no constructor matches");
+ }
+}
diff --git a/sample/evolve/WebPage.java b/sample/evolve/WebPage.java new file mode 100644 index 0000000..7d420fe --- /dev/null +++ b/sample/evolve/WebPage.java @@ -0,0 +1,17 @@ +package sample.evolve;
+
+import java.io.*;
+import java.util.*;
+
+/**
+ * Updatable class. DemoServer instantiates this class and calls
+ * show() on the created object.
+ */
+
+public class WebPage {
+ public void show(OutputStreamWriter out) throws IOException {
+ Calendar c = new GregorianCalendar();
+ out.write(c.getTime().toString());
+ out.write("<P><A HREF=\"demo.html\">Return to the home page.</A>");
+ }
+}
diff --git a/sample/evolve/demo.html b/sample/evolve/demo.html new file mode 100644 index 0000000..3eedf3d --- /dev/null +++ b/sample/evolve/demo.html @@ -0,0 +1,39 @@ +<H2>Class Evolution</H2>
+
+<P>This is a demonstration of the class evolution mechanism
+implemented with Javassist. This mechanism enables a Java program to
+reload an existing class file. Although the reloaded class file is
+not applied to exiting objects (the old class file is used for those
+objects), it is effective in newly created objects.
+
+<P>Since the reloading is transparently executed, no programming
+convention is needed. However, there are some limitations on possible
+changes of the class definition. For example, the new class definition
+must have the same set of methods as the old one. These limitations are
+necessary for keeping the type system consistent.
+
+
+<H3><a href="java.html">Run WebPage.show()</a></H3>
+
+<P>The web server creates a new <code>WebPage</code> object and
+calls <code>show()</code> on that object. This method works as
+if it is a CGI script or a servlet and you will see the html file
+produced by this method on your browser.
+
+<H3><a href="update.html">Change WebPage.class</a></H3>
+
+<P>The web server overwrites class file <code>WebPage.class</code>
+on the local disk. Then it signals that <code>WebPage.class</code>
+must be reloaded into the JVM. If you run <code>WebPage.show()</code>
+again, you will see a different page on your browser.
+
+<H3>Source files</H3>
+
+<P>Web server: <A HREF="DemoServer.java"><code>DemoServer.java</code></A>
+
+<P>WebPage: <A HREF="WebPage.java"><code>WebPage.java</code></A> and
+another <A HREF="sample/evolve/WebPage.java"><code>WebPage.java</code></A>
+
+<P>Class loader: <A HREF="DemoLoader.java"><code>DemoLoader.java</code></A>,
+ <A HREF="Evolution.java"><code>Evolution.java</code></A>, and
+ <A HREF="VersionManager.java"><code>VersionManager.java</code></A>.
diff --git a/sample/evolve/sample/evolve/WebPage.java b/sample/evolve/sample/evolve/WebPage.java new file mode 100644 index 0000000..507b956 --- /dev/null +++ b/sample/evolve/sample/evolve/WebPage.java @@ -0,0 +1,20 @@ +package sample.evolve;
+
+import java.io.*;
+import java.util.*;
+
+/**
+ * Updatable class. DemoServer instantiates this class and calls
+ * show() on the created object.
+ */
+
+public class WebPage {
+ public void show(OutputStreamWriter out) throws IOException {
+ out.write("<H2>Current Time:</H2>");
+ Calendar c = new GregorianCalendar();
+ out.write("<CENTER><H3><FONT color=\"blue\">");
+ out.write(c.getTime().toString());
+ out.write("</FONT></H3></CENTER><HR>");
+ out.write("<P><A HREF=\"demo.html\">Return to the home page.</A>");
+ }
+}
diff --git a/sample/evolve/start.html b/sample/evolve/start.html new file mode 100644 index 0000000..8ab3f94 --- /dev/null +++ b/sample/evolve/start.html @@ -0,0 +1,23 @@ +<h2>Instructions</h2>
+
+<p>1. Compile <code>sample/evolve/*.java</code>.
+
+<p>2. change the current directory to <code>sample/evolve</code><br>
+and compile there <code>sample/evolve/WebPage.java</code><br>
+(i.e. compile <code>sample/evolve/sample/evolve/WebPage.java</code>).
+
+<p>The two versions of <code>WebPage.class</code> are used<br>
+for changing the contents of <code>WebPage.class</code> during
+the demo.
+
+<p>3. Run the server on the local host (where your web browser is running):
+
+<ul>
+<code>% java sample.evolve.DemoLoader 5003
+</code></ul>
+
+<p>4. Click below:
+
+<ul><h2><a href="http://localhost:5003/demo.html">
+Start!
+</a></h2></ul>
diff --git a/sample/evolve/update.html b/sample/evolve/update.html new file mode 100644 index 0000000..8255165 --- /dev/null +++ b/sample/evolve/update.html @@ -0,0 +1,3 @@ +<h2><code>WebPage.class</code> has been changed.</h2>
+
+<a href="demo.html">Return to the home page.</a>
diff --git a/sample/hotswap/HelloWorld.java b/sample/hotswap/HelloWorld.java new file mode 100644 index 0000000..51d8fcd --- /dev/null +++ b/sample/hotswap/HelloWorld.java @@ -0,0 +1,5 @@ +public class HelloWorld { + public void print() { + System.out.println("hello world"); + } +} diff --git a/sample/hotswap/Test.java b/sample/hotswap/Test.java new file mode 100644 index 0000000..651d218 --- /dev/null +++ b/sample/hotswap/Test.java @@ -0,0 +1,25 @@ +import java.io.*; +import javassist.util.HotSwapper; + +public class Test { + public static void main(String[] args) throws Exception { + HotSwapper hs = new HotSwapper(8000); + new HelloWorld().print(); + + File newfile = new File("logging/HelloWorld.class"); + byte[] bytes = new byte[(int)newfile.length()]; + new FileInputStream(newfile).read(bytes); + System.out.println("** reload a logging version"); + + hs.reload("HelloWorld", bytes); + new HelloWorld().print(); + + newfile = new File("HelloWorld.class"); + bytes = new byte[(int)newfile.length()]; + new FileInputStream(newfile).read(bytes); + System.out.println("** reload the original version"); + + hs.reload("HelloWorld", bytes); + new HelloWorld().print(); + } +} diff --git a/sample/hotswap/logging/HelloWorld.java b/sample/hotswap/logging/HelloWorld.java new file mode 100644 index 0000000..88f0866 --- /dev/null +++ b/sample/hotswap/logging/HelloWorld.java @@ -0,0 +1,6 @@ +public class HelloWorld { + public void print() { + System.out.println("** HelloWorld.print()"); + System.out.println("hello world"); + } +} diff --git a/sample/preproc/Assistant.java b/sample/preproc/Assistant.java new file mode 100644 index 0000000..81b93b2 --- /dev/null +++ b/sample/preproc/Assistant.java @@ -0,0 +1,53 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2005 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package sample.preproc; + +import javassist.CtClass; +import javassist.CannotCompileException; +import javassist.ClassPool; + +/** + * This is an interface for objects invoked by the + * Javassist preprocessor when the preprocessor encounters an annotated + * import declaration. + * + * @see sample.preproc.Compiler + */ +public interface Assistant { + /** + * Is called when the Javassist preprocessor encounters an + * import declaration annotated with the "by" keyword. + * + * <p>The original import declaration is replaced with new import + * declarations of classes returned by this method. For example, + * the following implementation does not change the original + * declaration: + * + * <ul><pre> + * public CtClass[] assist(ClassPool cp, String importname, String[] args) { + * return new CtClass[] { cp.get(importname) }; + * } + * </pre></uL> + * + * @param cp class pool + * @param importname the class imported by the declaration + * @param args the parameters specified by the annotation + * @return the classes imported in the java source + * program produced by the preprocessor. + */ + public CtClass[] assist(ClassPool cp, String importname, + String[] args) throws CannotCompileException; +} diff --git a/sample/preproc/Compiler.java b/sample/preproc/Compiler.java new file mode 100644 index 0000000..5481c0f --- /dev/null +++ b/sample/preproc/Compiler.java @@ -0,0 +1,352 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2005 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package sample.preproc; + +import java.io.IOException; +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.BufferedWriter; +import java.io.FileWriter; +import java.util.Vector; +import javassist.CannotCompileException; +import javassist.CtClass; +import javassist.ClassPool; + +/** + * This is a preprocessor for Java source programs using annotated + * import declarations. + * + * <ul><pre> + * import <i>class-name</i> by <i>assistant-name</i> [(<i>arg1, arg2, ...</i>)] + * </pre></ul> + * + * <p>To process this annotation, run this class as follows: + * + * <ul><pre> + * java sample.preproc.Compiler sample.j + * </pre></ul> + * + * <p>This command produces <code>sample.java</code>, which only includes + * regular import declarations. Also, the Javassist program + * specified by <i>assistant-name</i> is executed so that it produces + * class files under the <code>./tmpjvst</code> directory. The class + * specified by <i>assistant-name</i> must implement + * <code>sample.preproc.Assistant</code>. + * + * @see sample.preproc.Assistant + */ + +public class Compiler { + protected BufferedReader input; + protected BufferedWriter output; + protected ClassPool classPool; + + /** + * Constructs a <code>Compiler</code> with a source file. + * + * @param inputname the name of the source file. + */ + public Compiler(String inputname) throws CannotCompileException { + try { + input = new BufferedReader(new FileReader(inputname)); + } + catch (IOException e) { + throw new CannotCompileException("cannot open: " + inputname); + } + + String outputname = getOutputFilename(inputname); + if (outputname.equals(inputname)) + throw new CannotCompileException("invalid source name: " + + inputname); + + try { + output = new BufferedWriter(new FileWriter(outputname)); + } + catch (IOException e) { + throw new CannotCompileException("cannot open: " + outputname); + } + + classPool = ClassPool.getDefault(); + } + + /** + * Starts preprocessing. + */ + public void process() throws IOException, CannotCompileException { + int c; + CommentSkipper reader = new CommentSkipper(input, output); + while ((c = reader.read()) != -1) { + output.write(c); + if (c == 'p') { + if (skipPackage(reader)) + break; + } + else if (c == 'i') + readImport(reader); + else if (c != ' ' && c != '\t' && c != '\n' && c != '\r') + break; + } + + while ((c = input.read()) != -1) + output.write(c); + + input.close(); + output.close(); + } + + private boolean skipPackage(CommentSkipper reader) throws IOException { + int c; + c = reader.read(); + output.write(c); + if (c != 'a') + return true; + + while ((c = reader.read()) != -1) { + output.write(c); + if (c == ';') + break; + } + + return false; + } + + private void readImport(CommentSkipper reader) + throws IOException, CannotCompileException + { + int word[] = new int[5]; + int c; + for (int i = 0; i < 5; ++i) { + word[i] = reader.read(); + output.write(word[i]); + } + + if (word[0] != 'm' || word[1] != 'p' || word[2] != 'o' + || word[3] != 'r' || word[4] != 't') + return; // syntax error? + + c = skipSpaces(reader, ' '); + StringBuffer classbuf = new StringBuffer(); + while (c != ' ' && c != '\t' && c != '\n' && c != '\r' + && c != ';' && c != -1) { + classbuf.append((char)c); + c = reader.read(); + } + + String importclass = classbuf.toString(); + c = skipSpaces(reader, c); + if (c == ';') { + output.write(importclass); + output.write(';'); + return; + } + if (c != 'b') + syntaxError(importclass); + + reader.read(); // skip 'y' + + StringBuffer assistant = new StringBuffer(); + Vector args = new Vector(); + c = readAssistant(reader, importclass, assistant, args); + c = skipSpaces(reader, c); + if (c != ';') + syntaxError(importclass); + + runAssistant(importclass, assistant.toString(), args); + } + + void syntaxError(String importclass) throws CannotCompileException { + throw new CannotCompileException("Syntax error. Cannot import " + + importclass); + } + + int readAssistant(CommentSkipper reader, String importclass, + StringBuffer assistant, Vector args) + throws IOException, CannotCompileException + { + int c = readArgument(reader, assistant); + c = skipSpaces(reader, c); + if (c == '(') { + do { + StringBuffer arg = new StringBuffer(); + c = readArgument(reader, arg); + args.addElement(arg.toString()); + c = skipSpaces(reader, c); + } while (c == ','); + + if (c != ')') + syntaxError(importclass); + + return reader.read(); + } + + return c; + } + + int readArgument(CommentSkipper reader, StringBuffer buf) + throws IOException + { + int c = skipSpaces(reader, ' '); + while ('A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' + || '0' <= c && c <= '9' || c == '.' || c == '_') { + buf.append((char)c); + c = reader.read(); + } + + return c; + } + + int skipSpaces(CommentSkipper reader, int c) throws IOException { + while (c == ' ' || c == '\t' || c == '\n' || c == '\r') { + if (c == '\n' || c == '\r') + output.write(c); + + c = reader.read(); + } + + return c; + } + + /** + * Is invoked if this compiler encoutenrs: + * + * <ul><pre> + * import <i>class name</i> by <i>assistant</i> (<i>args1</i>, <i>args2</i>, ...); + * </pre></ul> + * + * @param classname class name + * @param assistantname assistant + * @param argv args1, args2, ... + */ + private void runAssistant(String importname, String assistantname, + Vector argv) + throws IOException, CannotCompileException + { + Class assistant; + Assistant a; + int s = argv.size(); + String[] args = new String[s]; + for (int i = 0; i < s; ++i) + args[i] = (String)argv.elementAt(i); + + try { + assistant = Class.forName(assistantname); + } + catch (ClassNotFoundException e) { + throw new CannotCompileException("Cannot find " + assistantname); + } + + try { + a = (Assistant)assistant.newInstance(); + } + catch (Exception e) { + throw new CannotCompileException(e); + } + + CtClass[] imports = a.assist(classPool, importname, args); + s = imports.length; + if (s < 1) + output.write(" java.lang.Object;"); + else { + output.write(' '); + output.write(imports[0].getName()); + output.write(';'); + for (int i = 1; i < s; ++i) { + output.write(" import "); + output.write(imports[1].getName()); + output.write(';'); + } + } + } + + private String getOutputFilename(String input) { + int i = input.lastIndexOf('.'); + if (i < 0) + i = input.length(); + + return input.substring(0, i) + ".java"; + } + + public static void main(String[] args) { + if (args.length > 0) + try { + Compiler c = new Compiler(args[0]); + c.process(); + } + catch (IOException e) { + System.err.println(e); + } + catch (CannotCompileException e) { + System.err.println(e); + } + else { + System.err.println("Javassist version " + CtClass.version); + System.err.println("No source file is specified."); + } + } +} + +class CommentSkipper { + private BufferedReader input; + private BufferedWriter output; + + public CommentSkipper(BufferedReader reader, BufferedWriter writer) { + input = reader; + output = writer; + } + + public int read() throws IOException { + int c; + while ((c = input.read()) != -1) + if (c != '/') + return c; + else { + c = input.read(); + if (c == '/') + skipCxxComments(); + else if (c == '*') + skipCComments(); + else + output.write('/'); + } + + return c; + } + + private void skipCxxComments() throws IOException { + int c; + output.write("//"); + while ((c = input.read()) != -1) { + output.write(c); + if (c == '\n' || c == '\r') + break; + } + } + + private void skipCComments() throws IOException { + int c; + boolean star = false; + output.write("/*"); + while ((c = input.read()) != -1) { + output.write(c); + if (c == '*') + star = true; + else if(star && c == '/') + break; + else + star = false; + } + } +} diff --git a/sample/preproc/package.html b/sample/preproc/package.html new file mode 100644 index 0000000..dee15e0 --- /dev/null +++ b/sample/preproc/package.html @@ -0,0 +1,14 @@ +<html> +<body> +A sample preprocessor. + +<p>The preprocessor for running Javassist at compile time. +The produced class files are saved on a local disk. + +<p>This package is provided as a sample implementation of the +preprocessor using Javassist. All the programs in this package +uses only the regular Javassist API; they never call any hidden +methods. + +</body> +</html> diff --git a/sample/reflect/Main.java b/sample/reflect/Main.java new file mode 100644 index 0000000..972e896 --- /dev/null +++ b/sample/reflect/Main.java @@ -0,0 +1,32 @@ +package sample.reflect;
+
+import javassist.tools.reflect.Loader;
+
+/*
+ The "verbose metaobject" example (JDK 1.2 or later only).
+
+ Since this program registers class Person as a reflective class
+ (in a more realistic demonstration, what classes are reflective
+ would be specified by some configuration file), the class loader
+ modifies Person.class when loading into the JVM so that the class
+ Person is changed into a reflective class and a Person object is
+ controlled by a VerboseMetaobj.
+
+ To run,
+
+ % java javassist.tools.reflect.Loader sample.reflect.Main Joe
+
+ Compare this result with that of the regular execution without reflection:
+
+ % java sample.reflect.Person Joe
+*/
+public class Main {
+ public static void main(String[] args) throws Throwable {
+ Loader cl = (Loader)Main.class.getClassLoader();
+ cl.makeReflective("sample.reflect.Person",
+ "sample.reflect.VerboseMetaobj",
+ "javassist.tools.reflect.ClassMetaobject");
+
+ cl.run("sample.reflect.Person", args);
+ }
+}
diff --git a/sample/reflect/Person.java b/sample/reflect/Person.java new file mode 100644 index 0000000..90ccb18 --- /dev/null +++ b/sample/reflect/Person.java @@ -0,0 +1,53 @@ +/*
+ * A base-level class controlled by VerboseMetaobj.
+ */
+
+package sample.reflect;
+
+import javassist.tools.reflect.Metalevel;
+import javassist.tools.reflect.Metaobject;
+
+public class Person {
+ public String name;
+
+ public static int birth = 3;
+
+ public static final String defaultName = "John";
+
+ public Person(String name, int birthYear) {
+ if (name == null)
+ this.name = defaultName;
+ else
+ this.name = name;
+
+ birth = birthYear;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public int getAge(int year) {
+ return year - birth;
+ }
+
+ public static void main(String[] args) {
+ String name;
+ if (args.length > 0)
+ name = args[0];
+ else
+ name = "Bill";
+
+ Person p = new Person(name, 1960);
+ System.out.println("name: " + p.getName());
+ System.out.println("object: " + p.toString());
+
+ // change the metaobject of p.
+ if (p instanceof Metalevel) {
+ ((Metalevel)p)._setMetaobject(new Metaobject(p, null));
+ System.out.println("<< the metaobject was changed.>>");
+ }
+
+ System.out.println("age: " + p.getAge(1999));
+ }
+}
diff --git a/sample/reflect/VerboseMetaobj.java b/sample/reflect/VerboseMetaobj.java new file mode 100644 index 0000000..cc47999 --- /dev/null +++ b/sample/reflect/VerboseMetaobj.java @@ -0,0 +1,27 @@ +package sample.reflect;
+
+import javassist.tools.reflect.*;
+
+public class VerboseMetaobj extends Metaobject {
+ public VerboseMetaobj(Object self, Object[] args) {
+ super(self, args);
+ System.out.println("** constructed: " + self.getClass().getName());
+ }
+
+ public Object trapFieldRead(String name) {
+ System.out.println("** field read: " + name);
+ return super.trapFieldRead(name);
+ }
+
+ public void trapFieldWrite(String name, Object value) {
+ System.out.println("** field write: " + name);
+ super.trapFieldWrite(name, value);
+ }
+
+ public Object trapMethodcall(int identifier, Object[] args)
+ throws Throwable {
+ System.out.println("** trap: " + getMethodName(identifier) + "() in "
+ + getClassMetaobject().getName());
+ return super.trapMethodcall(identifier, args);
+ }
+}
diff --git a/sample/rmi/AlertDialog.java b/sample/rmi/AlertDialog.java new file mode 100644 index 0000000..99fae5c --- /dev/null +++ b/sample/rmi/AlertDialog.java @@ -0,0 +1,30 @@ +package sample.rmi;
+
+import java.awt.*;
+import java.awt.event.*;
+
+public class AlertDialog extends Frame implements ActionListener {
+ private Label label;
+
+ public AlertDialog() {
+ super("Alert");
+ setSize(200, 100);
+ setLocation(100, 100);
+ label = new Label();
+ Button b = new Button("OK");
+ b.addActionListener(this);
+ Panel p = new Panel();
+ p.add(b);
+ add("North", label);
+ add("South", p);
+ }
+
+ public void show(String message) {
+ label.setText(message);
+ setVisible(true);
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ setVisible(false);
+ }
+}
diff --git a/sample/rmi/CountApplet.java b/sample/rmi/CountApplet.java new file mode 100644 index 0000000..e4ee0ee --- /dev/null +++ b/sample/rmi/CountApplet.java @@ -0,0 +1,83 @@ +package sample.rmi;
+
+import java.applet.*;
+import java.awt.*;
+import java.awt.event.*;
+import javassist.tools.rmi.ObjectImporter;
+import javassist.tools.rmi.ObjectNotFoundException;
+import javassist.tools.web.Viewer;
+
+public class CountApplet extends Applet implements ActionListener {
+ private Font font;
+ private ObjectImporter importer;
+ private Counter counter;
+ private AlertDialog dialog;
+ private String message;
+
+ private String paramButton;
+ private String paramName;
+
+ public void init() {
+ paramButton = getParameter("button");
+ paramName = getParameter("name");
+ importer = new ObjectImporter(this);
+ commonInit();
+ }
+
+ /* call this method instead of init() if this program is not run
+ * as an applet.
+ */
+ public void applicationInit() {
+ paramButton = "OK";
+ paramName = "counter";
+ Viewer cl = (Viewer)getClass().getClassLoader();
+ importer = new ObjectImporter(cl.getServer(), cl.getPort());
+ commonInit();
+ }
+
+ private void commonInit() {
+ font = new Font("SansSerif", Font.ITALIC, 40);
+ Button b = new Button(paramButton);
+ b.addActionListener(this);
+ add(b);
+ dialog = new AlertDialog();
+ message = "???";
+ }
+
+ public void destroy() {
+ dialog.dispose();
+ }
+
+ public void start() {
+ try {
+ counter = (Counter)importer.lookupObject(paramName);
+ message = Integer.toString(counter.get());
+ }
+ catch (ObjectNotFoundException e) {
+ dialog.show(e.toString());
+ }
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ counter.increase();
+ message = Integer.toString(counter.get());
+ repaint();
+ }
+
+ public void paint(Graphics g) {
+ g.setFont(font);
+ g.drawRect(50, 50, 100, 100);
+ g.setColor(Color.blue);
+ g.drawString(message, 60, 120);
+ }
+
+ public static void main(String[] args) {
+ Frame f = new Frame("CountApplet");
+ CountApplet ca = new CountApplet();
+ f.add(ca);
+ f.setSize(300, 300);
+ ca.applicationInit();
+ ca.start();
+ f.setVisible(true);
+ }
+}
diff --git a/sample/rmi/Counter.java b/sample/rmi/Counter.java new file mode 100644 index 0000000..0920ca7 --- /dev/null +++ b/sample/rmi/Counter.java @@ -0,0 +1,32 @@ +package sample.rmi;
+
+import javassist.tools.rmi.AppletServer;
+import java.io.IOException;
+import javassist.CannotCompileException;
+import javassist.NotFoundException;
+
+public class Counter {
+ private int count = 0;
+
+ public int get() {
+ return count;
+ }
+
+ synchronized public int increase() {
+ count += 1;
+ return count;
+ }
+
+ public static void main(String[] args)
+ throws IOException, NotFoundException, CannotCompileException
+ {
+ if (args.length == 1) {
+ AppletServer web = new AppletServer(args[0]);
+ web.exportObject("counter", new Counter());
+ web.run();
+ }
+ else
+ System.err.println(
+ "Usage: java sample.rmi.Counter <port number>");
+ }
+}
diff --git a/sample/rmi/inside.gif b/sample/rmi/inside.gif Binary files differnew file mode 100644 index 0000000..c69c8ee --- /dev/null +++ b/sample/rmi/inside.gif diff --git a/sample/rmi/start.html b/sample/rmi/start.html new file mode 100644 index 0000000..33321ad --- /dev/null +++ b/sample/rmi/start.html @@ -0,0 +1,15 @@ +<h2>Instructions</h2>
+
+<p>1. Run the server on the local host (where your web browser is running):
+
+<ul>% java sample.rmi.Counter 5001</ul>
+
+<p>2. Click below:
+
+<h2><a href="webdemo.html">
+Start!
+</a></h2>
+
+<p>If you don't want to use a web browser, do as follows:
+
+<ul><pre>% java javassist.tools.web.Viewer localhost 5001 sample.rmi.CountApplet</pre></ul>
diff --git a/sample/rmi/webdemo.html b/sample/rmi/webdemo.html new file mode 100644 index 0000000..a2b595c --- /dev/null +++ b/sample/rmi/webdemo.html @@ -0,0 +1,203 @@ +<html>
+<body>
+<h2>Remote Method Invocation</h2>
+
+<P>Javassist enables an applet to access a remote object as if it is a
+local object. The applet can communicate through a socket with the
+host that executes the web server distributing that applet. However,
+the applet cannot directly call a method on an object if the object is
+on a remote host. The <code>javassist.tools.rmi</code> package provides
+a mechanism for the applet to transparently access the remote object.
+The rules that the applet must be subject to are simpler than the
+standard Java RMI.
+
+<h3>1. Sample applet</h3>
+
+<P>The applet showing below is a simple number counter.
+If you press the button, the number is increased by one.
+An interesting feature of this applet is that the object
+recording the current number is contained by the web server
+written in Java. The applet must access the object through a socket
+to obtain the current number.
+
+<p><center>
+<applet codebase="http://localhost:5001"
+code="sample.rmi.CountApplet" width=200 height=200>
+<param name=name value="counter">
+<param name=button value="+1">
+</applet>
+</center>
+
+<p>However, the program of the applet does not need to directly handle
+a socket. The <code>ObjectImporter</code> provided by Javassist deals
+with all the awkward programming.
+Look at the lines shown with red:
+
+<p><b>Figure 1: Applet</b>
+
+<pre>
+<font color="red">import javassist.tools.rmi.ObjectImporter;</font>
+
+public class CountApplet extends Applet implements ActionListener {
+ private Font font;
+ <font color="red">private ObjectImporter importer;
+ private Counter counter;</font>
+ private AlertDialog dialog;
+ private String message;
+
+ public void init() {
+ font = new Font("SansSerif", Font.ITALIC, 40);
+ Button b = new Button(getParameter("button"));
+ b.addActionListener(this);
+ add(b);
+ <font color="red">importer = new ObjectImporter(this);</font>
+ dialog = new AlertDialog();
+ message = "???";
+ }
+
+ public void start() {
+ String counterName = getParameter("name");
+ <font color="red">counter = (Counter)importer.getObject(counterName);</font>
+ message = Integer.toString(<font color="red">counter.get()</font>);
+ }
+
+ /* The method called when the button is pressed.
+ */
+ public void actionPerformed(ActionEvent e) {
+ message = Integer.toString(<font color="red">counter.increase()</font>);
+ repaint();
+ }
+
+ public void paint(Graphics g) {
+ g.setFont(font);
+ g.drawRect(50, 50, 100, 100);
+ g.setColor(Color.blue);
+ g.drawString(message, 60, 120);
+ }
+}
+</pre>
+
+<p>A <code>Counter</code> object running on a remote host
+maintains the counter number. To access this object, the applet first
+calls <code>getObject()</code> on an <code>ObjectImporter</code>
+to obtain a reference to the object. The parameter is the name associated
+with the object by the web server. Once the reference is obtained, it
+is delt with as if it is a reference to a local object.
+For example, <code>counter.get()</code> and <code>counter.increase()</code>
+call methods on the remote object.
+
+<p>The definition of the <code>Counter</code> class is also
+straightforward:
+
+<p><b>Figure 2: Remote object</b>
+
+<pre>
+public class Counter {
+ private int count = 0;
+
+ public int get() {
+ return count;
+ }
+
+ public int increase() {
+ count += 1;
+ return count;
+ }
+}
+</pre>
+
+<p>Note that the <code>javassist.tools.rmi</code> package does not require
+the <code>Counter</code> class to be an interface unlike the Java RMI,
+with which <code>Counter</code> must be an interface and it must be
+implemented by another class.
+
+<p>To make the <code>Counter</code> object available from the applet,
+it must be registered with the web server. A <code>AppletServer</code>
+object is a simple webserver that can distribute <code>.html</code> files
+and <code>.class</code> files (Java applets).
+
+<p><b>Figure 3: Server-side program</b>
+
+<pre>
+public class MyWebServer {
+ public static void main(String[] args) throws IOException, CannotCompileException
+ {
+ AppletServer web = new AppletServer(args[0]);
+ <font color="red">web.exportObject("counter", new Counter());</font>
+ web.run();
+ }
+}
+</pre>
+
+<p>The <code>exportObject()</code> method registers a remote object
+with the <code>AppletServer</code> object. In the example above,
+a <code>Counter</code> object is registered. The applet can access
+the object with the name "counter". The web server starts the service
+if the <code>run()</code> method is called.
+
+<p><br>
+
+<h3>2. Features</h3>
+
+The remote method invocation mechanism provided by Javassist has the
+following features:
+
+<ul>
+<li><b>Regular Java syntax:</b><br>
+ The applet can call a method on a remote object with regular
+ Java syntax.
+<p>
+
+<li><b>No special naming convention:</b><br>
+ The applet can use the same class name as the server-side program.
+ The reference object to a remote <code>Foo</code> object is
+ also represented by the class <code>Foo</code>.
+ Unlike other similar
+ systems, it is not represented by a different class such as
+ <code>ProxyFoo</code> or an interface implemented by
+ <code>Foo</code>.
+<p>
+
+<li><b>No extra compiler:</b><br>
+ All the programs, both the applet and the server-side program,
+ are compiled by the regular Java compiler. No external compiler
+ is needed.
+</ul>
+
+<p> With the Java RMI or Voyager, the applet programmer must define
+an interface for every remote object class and access the remote object
+through that interface.
+On the other hand, the <code>javassist.tools.rmi</code> package does not
+require the programmer to follow that programming convention.
+It is suitable for writing simple distributed programs like applets.
+
+<p><br>
+
+<h3>3. Inside of the system</h3>
+
+<p>A key idea of the implementation is that the applet and the server-side
+program must use different versions of the class <code>Counter</code>.
+The <code>Counter</code> object in the applet must work as a proxy
+object, which transfers the method invocations to the <code>Counter</code>
+object in the server-side program.
+
+<p>With other systems like the Java RMI, the class of this proxy object is
+produced by a special compiler such as <code>rmic</code>.
+It must be manually maintained by the programmer.
+
+<center><img src="inside.gif"></center>
+
+<p>However, Javassist automatically generates the proxy class at
+runtime so that the programmer does not have to be concerned about the
+maintenance of the proxy class.
+If the web browser running the applet
+requests to load the <code>Counter</code> class, which is the class
+of an exported object,
+then the web server
+transfers the version of <code>Counter</code> that Javassist generates
+as a proxy class.
+
+<p><br>
+
+</body>
+</html>
diff --git a/sample/vector/Sample.java b/sample/vector/Sample.java new file mode 100644 index 0000000..7a47aad --- /dev/null +++ b/sample/vector/Sample.java @@ -0,0 +1,14 @@ +package sample.vector;
+
+public class Sample extends java.util.Vector {
+ public void add(X e) {
+ super.addElement(e);
+ }
+
+ public X at(int i) {
+ return (X)super.elementAt(i);
+ }
+}
+
+class X {
+}
diff --git a/sample/vector/Sample2.java b/sample/vector/Sample2.java new file mode 100644 index 0000000..dd5c965 --- /dev/null +++ b/sample/vector/Sample2.java @@ -0,0 +1,13 @@ +package sample.vector;
+
+public class Sample2 extends java.util.Vector {
+ public Object add(Object[] args) {
+ super.addElement(args[0]);
+ return null;
+ }
+
+ public Object at(Object[] args) {
+ int i = ((Integer)args[0]).intValue();
+ return super.elementAt(i);
+ }
+}
diff --git a/sample/vector/Test.j b/sample/vector/Test.j new file mode 100644 index 0000000..4ae7f6b --- /dev/null +++ b/sample/vector/Test.j @@ -0,0 +1,38 @@ +/* + A sample program using sample.vector.VectorAssistant + and the sample.preproc package. + + This automatically produces the classes representing vectors of integer + and vectors of java.lang.String. + + To compile and run this program, do as follows: + + % java sample.preproc.Compiler sample/vector/Test.j + % javac sample/vector/Test.java + % java sample.vector.Test + + The first line produces one source file (sample/Test.java) and + two class files (sample/vector/intVector.class and + sample/vector/StringVector.class). +*/ + +package sample.vector; + +import java.util.Vector by sample.vector.VectorAssistant(java.lang.String); +import java.util.Vector by sample.vector.VectorAssistant(int); + +public class Test { + public static void main(String[] args) { + intVector iv = new intVector(); + iv.add(3); + iv.add(4); + for (int i = 0; i < iv.size(); ++i) + System.out.println(iv.at(i)); + + StringVector sv = new StringVector(); + sv.add("foo"); + sv.add("bar"); + for (int i = 0; i < sv.size(); ++i) + System.out.println(sv.at(i)); + } +} diff --git a/sample/vector/VectorAssistant.java b/sample/vector/VectorAssistant.java new file mode 100644 index 0000000..8b721db --- /dev/null +++ b/sample/vector/VectorAssistant.java @@ -0,0 +1,135 @@ +package sample.vector; + +import java.io.IOException; +import javassist.*; +import sample.preproc.Assistant; + +/** + * This is a Javassist program which produce a new class representing + * vectors of a given type. For example, + * + * <ul>import java.util.Vector by sample.vector.VectorAssistant(int)</ul> + * + * <p>requests the Javassist preprocessor to substitute the following + * lines for the original import declaration: + * + * <ul><pre> + * import java.util.Vector; + * import sample.vector.intVector; + * </pre></ul> + * + * <p>The Javassist preprocessor calls <code>VectorAssistant.assist()</code> + * and produces class <code>intVector</code> equivalent to: + * + * <ul><pre> + * package sample.vector; + * + * public class intVector extends Vector { + * pubilc void add(int value) { + * addElement(new Integer(value)); + * } + * + * public int at(int index) { + * return elementAt(index).intValue(); + * } + * } + * </pre></ul> + * + * <p><code>VectorAssistant.assist()</code> uses + * <code>sample.vector.Sample</code> and <code>sample.vector.Sample2</code> + * as a template to produce the methods <code>add()</code> and + * <code>at()</code>. + */ +public class VectorAssistant implements Assistant { + public final String packageName = "sample.vector."; + + /** + * Calls <code>makeSubclass()</code> and produces a new vector class. + * This method is called by a <code>sample.preproc.Compiler</code>. + * + * @see sample.preproc.Compiler + */ + public CtClass[] assist(ClassPool pool, String vec, String[] args) + throws CannotCompileException + { + if (args.length != 1) + throw new CannotCompileException( + "VectorAssistant receives a single argument."); + + try { + CtClass subclass; + CtClass elementType = pool.get(args[0]); + if (elementType.isPrimitive()) + subclass = makeSubclass2(pool, elementType); + else + subclass = makeSubclass(pool, elementType); + + CtClass[] results = { subclass, pool.get(vec) }; + return results; + } + catch (NotFoundException e) { + throw new CannotCompileException(e); + } + catch (IOException e) { + throw new CannotCompileException(e); + } + } + + /** + * Produces a new vector class. This method does not work if + * the element type is a primitive type. + * + * @param type the type of elements + */ + public CtClass makeSubclass(ClassPool pool, CtClass type) + throws CannotCompileException, NotFoundException, IOException + { + CtClass vec = pool.makeClass(makeClassName(type)); + vec.setSuperclass(pool.get("java.util.Vector")); + + CtClass c = pool.get("sample.vector.Sample"); + CtMethod addmethod = c.getDeclaredMethod("add"); + CtMethod atmethod = c.getDeclaredMethod("at"); + + ClassMap map = new ClassMap(); + map.put("sample.vector.X", type.getName()); + + vec.addMethod(CtNewMethod.copy(addmethod, "add", vec, map)); + vec.addMethod(CtNewMethod.copy(atmethod, "at", vec, map)); + vec.writeFile(); + return vec; + } + + /** + * Produces a new vector class. This uses wrapped methods so that + * the element type can be a primitive type. + * + * @param type the type of elements + */ + public CtClass makeSubclass2(ClassPool pool, CtClass type) + throws CannotCompileException, NotFoundException, IOException + { + CtClass vec = pool.makeClass(makeClassName(type)); + vec.setSuperclass(pool.get("java.util.Vector")); + + CtClass c = pool.get("sample.vector.Sample2"); + CtMethod addmethod = c.getDeclaredMethod("add"); + CtMethod atmethod = c.getDeclaredMethod("at"); + + CtClass[] args1 = { type }; + CtClass[] args2 = { CtClass.intType }; + CtMethod m + = CtNewMethod.wrapped(CtClass.voidType, "add", args1, + null, addmethod, null, vec); + vec.addMethod(m); + m = CtNewMethod.wrapped(type, "at", args2, + null, atmethod, null, vec); + vec.addMethod(m); + vec.writeFile(); + return vec; + } + + private String makeClassName(CtClass type) { + return packageName + type.getSimpleName() + "Vector"; + } +} diff --git a/src/main/META-INF/MANIFEST.MF b/src/main/META-INF/MANIFEST.MF new file mode 100644 index 0000000..02ac494 --- /dev/null +++ b/src/main/META-INF/MANIFEST.MF @@ -0,0 +1,8 @@ +Manifest-Version: 1.1 +Specification-Title: Javassist +Created-By: Shigeru Chiba, Tokyo Institute of Technology +Specification-Vendor: Shigeru Chiba, Tokyo Institute of Technology +Specification-Version: 3.14.0.GA +Main-Class: javassist.CtClass + +Name: javassist/ diff --git a/src/main/javassist/ByteArrayClassPath.java b/src/main/javassist/ByteArrayClassPath.java new file mode 100644 index 0000000..f09f81f --- /dev/null +++ b/src/main/javassist/ByteArrayClassPath.java @@ -0,0 +1,98 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist; + +import java.io.*; +import java.net.URL; +import java.net.MalformedURLException; + +/** + * A <code>ByteArrayClassPath</code> contains bytes that is served as + * a class file to a <code>ClassPool</code>. It is useful to convert + * a byte array to a <code>CtClass</code> object. + * + * <p>For example, if you want to convert a byte array <code>b</code> + * into a <code>CtClass</code> object representing the class with a name + * <code>classname</code>, then do as following: + * + * <ul><pre> + * ClassPool cp = ClassPool.getDefault(); + * cp.insertClassPath(new ByteArrayClassPath(classname, b)); + * CtClass cc = cp.get(classname); + * </pre></ul> + * + * <p>The <code>ClassPool</code> object <code>cp</code> uses the created + * <code>ByteArrayClassPath</code> object as the source of the class file. + * + * <p>A <code>ByteArrayClassPath</code> must be instantiated for every + * class. It contains only a single class file. + * + * @see javassist.ClassPath + * @see ClassPool#insertClassPath(ClassPath) + * @see ClassPool#appendClassPath(ClassPath) + * @see ClassPool#makeClass(InputStream) + */ +public class ByteArrayClassPath implements ClassPath { + protected String classname; + protected byte[] classfile; + + /* + * Creates a <code>ByteArrayClassPath</code> containing the given + * bytes. + * + * @param name a fully qualified class name + * @param classfile the contents of a class file. + */ + public ByteArrayClassPath(String name, byte[] classfile) { + this.classname = name; + this.classfile = classfile; + } + + /** + * Closes this class path. + */ + public void close() {} + + public String toString() { + return "byte[]:" + classname; + } + + /** + * Opens the class file. + */ + public InputStream openClassfile(String classname) { + if(this.classname.equals(classname)) + return new ByteArrayInputStream(classfile); + else + return null; + } + + /** + * Obtains the URL. + */ + public URL find(String classname) { + if(this.classname.equals(classname)) { + String cname = classname.replace('.', '/') + ".class"; + try { + // return new File(cname).toURL(); + return new URL("file:/ByteArrayClassPath/" + cname); + } + catch (MalformedURLException e) {} + } + + return null; + } +} diff --git a/src/main/javassist/CannotCompileException.java b/src/main/javassist/CannotCompileException.java new file mode 100644 index 0000000..35ad6e7 --- /dev/null +++ b/src/main/javassist/CannotCompileException.java @@ -0,0 +1,119 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist; + +import javassist.compiler.CompileError; + +/** + * Thrown when bytecode transformation has failed. + */ +public class CannotCompileException extends Exception { + private Throwable myCause; + + /** + * Gets the cause of this throwable. + * It is for JDK 1.3 compatibility. + */ + public Throwable getCause() { + return (myCause == this ? null : myCause); + } + + /** + * Initializes the cause of this throwable. + * It is for JDK 1.3 compatibility. + */ + public synchronized Throwable initCause(Throwable cause) { + myCause = cause; + return this; + } + + private String message; + + /** + * Gets a long message if it is available. + */ + public String getReason() { + if (message != null) + return message; + else + return this.toString(); + } + + /** + * Constructs a CannotCompileException with a message. + * + * @param msg the message. + */ + public CannotCompileException(String msg) { + super(msg); + message = msg; + initCause(null); + } + + /** + * Constructs a CannotCompileException with an <code>Exception</code> + * representing the cause. + * + * @param e the cause. + */ + public CannotCompileException(Throwable e) { + super("by " + e.toString()); + message = null; + initCause(e); + } + + /** + * Constructs a CannotCompileException with a detailed message + * and an <code>Exception</code> representing the cause. + * + * @param msg the message. + * @param e the cause. + */ + public CannotCompileException(String msg, Throwable e) { + this(msg); + initCause(e); + } + + /** + * Constructs a CannotCompileException with a + * <code>NotFoundException</code>. + */ + public CannotCompileException(NotFoundException e) { + this("cannot find " + e.getMessage(), e); + } + + /** + * Constructs a CannotCompileException with an <code>CompileError</code>. + */ + public CannotCompileException(CompileError e) { + this("[source error] " + e.getMessage(), e); + } + + /** + * Constructs a CannotCompileException + * with a <code>ClassNotFoundException</code>. + */ + public CannotCompileException(ClassNotFoundException e, String name) { + this("cannot find " + name, e); + } + + /** + * Constructs a CannotCompileException with a ClassFormatError. + */ + public CannotCompileException(ClassFormatError e, String name) { + this("invalid class format: " + name, e); + } +} diff --git a/src/main/javassist/ClassClassPath.java b/src/main/javassist/ClassClassPath.java new file mode 100644 index 0000000..c9925d8 --- /dev/null +++ b/src/main/javassist/ClassClassPath.java @@ -0,0 +1,96 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist; + +import java.io.InputStream; +import java.net.URL; + +/** + * A search-path for obtaining a class file + * by <code>getResourceAsStream()</code> in <code>java.lang.Class</code>. + * + * <p>Try adding a <code>ClassClassPath</code> when a program is running + * with a user-defined class loader and any class files are not found with + * the default <code>ClassPool</code>. For example, + * + * <ul><pre> + * ClassPool cp = ClassPool.getDefault(); + * cp.insertClassPath(new ClassClassPath(this.getClass())); + * </pre></ul> + * + * This code snippet permanently adds a <code>ClassClassPath</code> + * to the default <code>ClassPool</code>. Note that the default + * <code>ClassPool</code> is a singleton. The added + * <code>ClassClassPath</code> uses a class object representing + * the class including the code snippet above. + * + * @see ClassPool#insertClassPath(ClassPath) + * @see ClassPool#appendClassPath(ClassPath) + * @see LoaderClassPath + */ +public class ClassClassPath implements ClassPath { + private Class thisClass; + + /** Creates a search path. + * + * @param c the <code>Class</code> object used to obtain a class + * file. <code>getResourceAsStream()</code> is called on + * this object. + */ + public ClassClassPath(Class c) { + thisClass = c; + } + + ClassClassPath() { + /* The value of thisClass was this.getClass() in early versions: + * + * thisClass = this.getClass(); + * + * However, this made openClassfile() not search all the system + * class paths if javassist.jar is put in jre/lib/ext/ + * (with JDK1.4). + */ + this(java.lang.Object.class); + } + + /** + * Obtains a class file by <code>getResourceAsStream()</code>. + */ + public InputStream openClassfile(String classname) { + String jarname = "/" + classname.replace('.', '/') + ".class"; + return thisClass.getResourceAsStream(jarname); + } + + /** + * Obtains the URL of the specified class file. + * + * @return null if the class file could not be found. + */ + public URL find(String classname) { + String jarname = "/" + classname.replace('.', '/') + ".class"; + return thisClass.getResource(jarname); + } + + /** + * Does nothing. + */ + public void close() { + } + + public String toString() { + return thisClass.getName() + ".class"; + } +} diff --git a/src/main/javassist/ClassMap.java b/src/main/javassist/ClassMap.java new file mode 100644 index 0000000..e923a38 --- /dev/null +++ b/src/main/javassist/ClassMap.java @@ -0,0 +1,170 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist; + +import javassist.bytecode.Descriptor; + +/** + * A hash table associating class names with different names. + * + * <p>This hashtable is used for replacing class names in a class + * definition or a method body. Define a subclass of this class + * if a more complex mapping algorithm is needed. For example, + * + * <ul><pre>class MyClassMap extends ClassMap { + * public Object get(Object jvmClassName) { + * String name = toJavaName((String)jvmClassName); + * if (name.startsWith("java.")) + * return toJvmName("java2." + name.substring(5)); + * else + * return super.get(jvmClassName); + * } + * }</pre></ul> + * + * <p>This subclass maps <code>java.lang.String</code> to + * <code>java2.lang.String</code>. Note that <code>get()</code> + * receives and returns the internal representation of a class name. + * For example, the internal representation of <code>java.lang.String</code> + * is <code>java/lang/String</code>. + * + * @see #get(Object) + * @see CtClass#replaceClassName(ClassMap) + * @see CtNewMethod#copy(CtMethod,String,CtClass,ClassMap) + */ +public class ClassMap extends java.util.HashMap { + private ClassMap parent; + + /** + * Constructs a hash table. + */ + public ClassMap() { parent = null; } + + ClassMap(ClassMap map) { parent = map; } + + /** + * Maps a class name to another name in this hashtable. + * The names are obtained with calling <code>Class.getName()</code>. + * This method translates the given class names into the + * internal form used in the JVM before putting it in + * the hashtable. + * + * @param oldname the original class name + * @param newname the substituted class name. + */ + public void put(CtClass oldname, CtClass newname) { + put(oldname.getName(), newname.getName()); + } + + /** + * Maps a class name to another name in this hashtable. + * If the hashtable contains another mapping from the same + * class name, the old mapping is replaced. + * This method translates the given class names into the + * internal form used in the JVM before putting it in + * the hashtable. + * + * <p>If <code>oldname</code> is identical to + * <code>newname</code>, then this method does not + * perform anything; it does not record the mapping from + * <code>oldname</code> to <code>newname</code>. See + * <code>fix</code> method. + * + * @param oldname the original class name. + * @param newname the substituted class name. + * @see #fix(String) + */ + public void put(String oldname, String newname) { + if (oldname == newname) + return; + + String oldname2 = toJvmName(oldname); + String s = (String)get(oldname2); + if (s == null || !s.equals(oldname2)) + super.put(oldname2, toJvmName(newname)); + } + + /** + * Is equivalent to <code>put()</code> except that + * the given mapping is not recorded into the hashtable + * if another mapping from <code>oldname</code> is + * already included. + * + * @param oldname the original class name. + * @param newname the substituted class name. + */ + public void putIfNone(String oldname, String newname) { + if (oldname == newname) + return; + + String oldname2 = toJvmName(oldname); + String s = (String)get(oldname2); + if (s == null) + super.put(oldname2, toJvmName(newname)); + } + + protected final void put0(Object oldname, Object newname) { + super.put(oldname, newname); + } + + /** + * Returns the class name to wihch the given <code>jvmClassName</code> + * is mapped. A subclass of this class should override this method. + * + * <p>This method receives and returns the internal representation of + * class name used in the JVM. + * + * @see #toJvmName(String) + * @see #toJavaName(String) + */ + public Object get(Object jvmClassName) { + Object found = super.get(jvmClassName); + if (found == null && parent != null) + return parent.get(jvmClassName); + else + return found; + } + + /** + * Prevents a mapping from the specified class name to another name. + */ + public void fix(CtClass clazz) { + fix(clazz.getName()); + } + + /** + * Prevents a mapping from the specified class name to another name. + */ + public void fix(String name) { + String name2 = toJvmName(name); + super.put(name2, name2); + } + + /** + * Converts a class name into the internal representation used in + * the JVM. + */ + public static String toJvmName(String classname) { + return Descriptor.toJvmName(classname); + } + + /** + * Converts a class name from the internal representation used in + * the JVM to the normal one used in Java. + */ + public static String toJavaName(String classname) { + return Descriptor.toJavaName(classname); + } +} diff --git a/src/main/javassist/ClassPath.java b/src/main/javassist/ClassPath.java new file mode 100644 index 0000000..d34b279 --- /dev/null +++ b/src/main/javassist/ClassPath.java @@ -0,0 +1,67 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist; + +import java.io.InputStream; +import java.net.URL; + +/** + * <code>ClassPath</code> is an interface implemented by objects + * representing a class search path. + * <code>ClassPool</code> uses those objects for reading class files. + * + * <p>The users can define a class implementing this interface so that + * a class file is obtained from a non-standard source. + * + * @see ClassPool#insertClassPath(ClassPath) + * @see ClassPool#appendClassPath(ClassPath) + * @see ClassPool#removeClassPath(ClassPath) + */ +public interface ClassPath { + /** + * Opens a class file. + * This method may be called just to examine whether the class file + * exists as well as to read the contents of the file. + * + * <p>This method can return null if the specified class file is not + * found. If null is returned, the next search path is examined. + * However, if an error happens, this method must throw an exception + * so that the search will be terminated. + * + * <p>This method should not modify the contents of the class file. + * + * @param classname a fully-qualified class name + * @return the input stream for reading a class file + * @see javassist.Translator + */ + InputStream openClassfile(String classname) throws NotFoundException; + + /** + * Returns the uniform resource locator (URL) of the class file + * with the specified name. + * + * @param classname a fully-qualified class name. + * @return null if the specified class file could not be found. + */ + URL find(String classname); + + /** + * This method is invoked when the <code>ClassPath</code> object is + * detached from the search path. It will be an empty method in most of + * classes. + */ + void close(); +} diff --git a/src/main/javassist/ClassPool.java b/src/main/javassist/ClassPool.java new file mode 100644 index 0000000..b75bfb6 --- /dev/null +++ b/src/main/javassist/ClassPool.java @@ -0,0 +1,1107 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.reflect.Method; +import java.net.URL; +import java.security.AccessController; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; +import java.security.ProtectionDomain; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.ArrayList; +import java.util.Enumeration; +import javassist.bytecode.Descriptor; + +/** + * A container of <code>CtClass</code> objects. + * A <code>CtClass</code> object must be obtained from this object. + * If <code>get()</code> is called on this object, + * it searches various sources represented by <code>ClassPath</code> + * to find a class file and then it creates a <code>CtClass</code> object + * representing that class file. The created object is returned to the + * caller. + * + * <p><b>Memory consumption memo:</b> + * + * <p><code>ClassPool</code> objects hold all the <code>CtClass</code>es + * that have been created so that the consistency among modified classes + * can be guaranteed. Thus if a large number of <code>CtClass</code>es + * are processed, the <code>ClassPool</code> will consume a huge amount + * of memory. To avoid this, a <code>ClassPool</code> object + * should be recreated, for example, every hundred classes processed. + * Note that <code>getDefault()</code> is a singleton factory. + * Otherwise, <code>detach()</code> in <code>CtClass</code> should be used + * to avoid huge memory consumption. + * + * <p><b><code>ClassPool</code> hierarchy:</b> + * + * <p><code>ClassPool</code>s can make a parent-child hierarchy as + * <code>java.lang.ClassLoader</code>s. If a <code>ClassPool</code> has + * a parent pool, <code>get()</code> first asks the parent pool to find + * a class file. Only if the parent could not find the class file, + * <code>get()</code> searches the <code>ClassPath</code>s of + * the child <code>ClassPool</code>. This search order is reversed if + * <code>ClassPath.childFirstLookup</code> is <code>true</code>. + * + * @see javassist.CtClass + * @see javassist.ClassPath + */ +public class ClassPool { + // used by toClass(). + private static java.lang.reflect.Method defineClass1, defineClass2; + + static { + try { + AccessController.doPrivileged(new PrivilegedExceptionAction(){ + public Object run() throws Exception{ + Class cl = Class.forName("java.lang.ClassLoader"); + defineClass1 = cl.getDeclaredMethod("defineClass", + new Class[] { String.class, byte[].class, + int.class, int.class }); + + defineClass2 = cl.getDeclaredMethod("defineClass", + new Class[] { String.class, byte[].class, + int.class, int.class, ProtectionDomain.class }); + return null; + } + }); + } + catch (PrivilegedActionException pae) { + throw new RuntimeException("cannot initialize ClassPool", pae.getException()); + } + } + + /** + * Determines the search order. + * + * <p>If this field is true, <code>get()</code> first searches the + * class path associated to this <code>ClassPool</code> and then + * the class path associated with the parent <code>ClassPool</code>. + * Otherwise, the class path associated with the parent is searched + * first. + * + * <p>The default value is false. + */ + public boolean childFirstLookup = false; + + /** + * Turning the automatic pruning on/off. + * + * <p>If this field is true, <code>CtClass</code> objects are + * automatically pruned by default when <code>toBytecode()</code> etc. + * are called. The automatic pruning can be turned on/off individually + * for each <code>CtClass</code> object. + * + * <p>The initial value is false. + * + * @see CtClass#prune() + * @see CtClass#stopPruning(boolean) + * @see CtClass#detach() + */ + public static boolean doPruning = false; + + private int compressCount; + private static final int COMPRESS_THRESHOLD = 100; + + /* releaseUnmodifiedClassFile was introduced for avoiding a bug + of JBoss AOP. So the value should be true except for JBoss AOP. + */ + + /** + * If true, unmodified and not-recently-used class files are + * periodically released for saving memory. + * + * <p>The initial value is true. + */ + public static boolean releaseUnmodifiedClassFile = true; + + protected ClassPoolTail source; + protected ClassPool parent; + protected Hashtable classes; // should be synchronous + + /** + * Table of registered cflow variables. + */ + private Hashtable cflow = null; // should be synchronous. + + private static final int INIT_HASH_SIZE = 191; + + private ArrayList importedPackages; + + /** + * Creates a root class pool. No parent class pool is specified. + */ + public ClassPool() { + this(null); + } + + /** + * Creates a root class pool. If <code>useDefaultPath</code> is + * true, <code>appendSystemPath()</code> is called. Otherwise, + * this constructor is equivalent to the constructor taking no + * parameter. + * + * @param useDefaultPath true if the system search path is + * appended. + */ + public ClassPool(boolean useDefaultPath) { + this(null); + if (useDefaultPath) + appendSystemPath(); + } + + /** + * Creates a class pool. + * + * @param parent the parent of this class pool. If this is a root + * class pool, this parameter must be <code>null</code>. + * @see javassist.ClassPool#getDefault() + */ + public ClassPool(ClassPool parent) { + this.classes = new Hashtable(INIT_HASH_SIZE); + this.source = new ClassPoolTail(); + this.parent = parent; + if (parent == null) { + CtClass[] pt = CtClass.primitiveTypes; + for (int i = 0; i < pt.length; ++i) + classes.put(pt[i].getName(), pt[i]); + } + + this.cflow = null; + this.compressCount = 0; + clearImportedPackages(); + } + + /** + * Returns the default class pool. + * The returned object is always identical since this method is + * a singleton factory. + * + * <p>The default class pool searches the system search path, + * which usually includes the platform library, extension + * libraries, and the search path specified by the + * <code>-classpath</code> option or the <code>CLASSPATH</code> + * environment variable. + * + * <p>When this method is called for the first time, the default + * class pool is created with the following code snippet: + * + * <ul><code>ClassPool cp = new ClassPool(); + * cp.appendSystemPath(); + * </code></ul> + * + * <p>If the default class pool cannot find any class files, + * try <code>ClassClassPath</code> and <code>LoaderClassPath</code>. + * + * @see ClassClassPath + * @see LoaderClassPath + */ + public static synchronized ClassPool getDefault() { + if (defaultPool == null) { + defaultPool = new ClassPool(null); + defaultPool.appendSystemPath(); + } + + return defaultPool; + } + + private static ClassPool defaultPool = null; + + /** + * Provide a hook so that subclasses can do their own + * caching of classes. + * + * @see #cacheCtClass(String,CtClass,boolean) + * @see #removeCached(String) + */ + protected CtClass getCached(String classname) { + return (CtClass)classes.get(classname); + } + + /** + * Provides a hook so that subclasses can do their own + * caching of classes. + * + * @see #getCached(String) + * @see #removeCached(String,CtClass) + */ + protected void cacheCtClass(String classname, CtClass c, boolean dynamic) { + classes.put(classname, c); + } + + /** + * Provide a hook so that subclasses can do their own + * caching of classes. + * + * @see #getCached(String) + * @see #cacheCtClass(String,CtClass,boolean) + */ + protected CtClass removeCached(String classname) { + return (CtClass)classes.remove(classname); + } + + /** + * Returns the class search path. + */ + public String toString() { + return source.toString(); + } + + /** + * This method is periodically invoked so that memory + * footprint will be minimized. + */ + void compress() { + if (compressCount++ > COMPRESS_THRESHOLD) { + compressCount = 0; + Enumeration e = classes.elements(); + while (e.hasMoreElements()) + ((CtClass)e.nextElement()).compress(); + } + } + + /** + * Record a package name so that the Javassist compiler searches + * the package to resolve a class name. + * Don't record the <code>java.lang</code> package, which has + * been implicitly recorded by default. + * + * <p>Since version 3.14, <code>packageName</code> can be a + * fully-qualified class name. + * + * <p>Note that <code>get()</code> in <code>ClassPool</code> does + * not search the recorded package. Only the compiler searches it. + * + * @param packageName the package name. + * It must not include the last '.' (dot). + * For example, "java.util" is valid but "java.util." is wrong. + * @since 3.1 + */ + public void importPackage(String packageName) { + importedPackages.add(packageName); + } + + /** + * Clear all the package names recorded by <code>importPackage()</code>. + * The <code>java.lang</code> package is not removed. + * + * @see #importPackage(String) + * @since 3.1 + */ + public void clearImportedPackages() { + importedPackages = new ArrayList(); + importedPackages.add("java.lang"); + } + + /** + * Returns all the package names recorded by <code>importPackage()</code>. + * + * @see #importPackage(String) + * @since 3.1 + */ + public Iterator getImportedPackages() { + return importedPackages.iterator(); + } + + /** + * Records a name that never exists. + * For example, a package name can be recorded by this method. + * This would improve execution performance + * since <code>get()</code> does not search the class path at all + * if the given name is an invalid name recorded by this method. + * Note that searching the class path takes relatively long time. + * + * @param name a class name (separeted by dot). + */ + public void recordInvalidClassName(String name) { + source.recordInvalidClassName(name); + } + + /** + * Records the <code>$cflow</code> variable for the field specified + * by <code>cname</code> and <code>fname</code>. + * + * @param name variable name + * @param cname class name + * @param fname field name + */ + void recordCflow(String name, String cname, String fname) { + if (cflow == null) + cflow = new Hashtable(); + + cflow.put(name, new Object[] { cname, fname }); + } + + /** + * Undocumented method. Do not use; internal-use only. + * + * @param name the name of <code>$cflow</code> variable + */ + public Object[] lookupCflow(String name) { + if (cflow == null) + cflow = new Hashtable(); + + return (Object[])cflow.get(name); + } + + /** + * Reads a class file and constructs a <code>CtClass</code> + * object with a new name. + * This method is useful if you want to generate a new class as a copy + * of another class (except the class name). For example, + * + * <ul><pre> + * getAndRename("Point", "Pair") + * </pre></ul> + * + * returns a <code>CtClass</code> object representing <code>Pair</code> + * class. The definition of <code>Pair</code> is the same as that of + * <code>Point</code> class except the class name since <code>Pair</code> + * is defined by reading <code>Point.class</code>. + * + * @param orgName the original (fully-qualified) class name + * @param newName the new class name + */ + public CtClass getAndRename(String orgName, String newName) + throws NotFoundException + { + CtClass clazz = get0(orgName, false); + if (clazz == null) + throw new NotFoundException(orgName); + + if (clazz instanceof CtClassType) + ((CtClassType)clazz).setClassPool(this); + + clazz.setName(newName); // indirectly calls + // classNameChanged() in this class + return clazz; + } + + /* + * This method is invoked by CtClassType.setName(). It removes a + * CtClass object from the hash table and inserts it with the new + * name. Don't delegate to the parent. + */ + synchronized void classNameChanged(String oldname, CtClass clazz) { + CtClass c = (CtClass)getCached(oldname); + if (c == clazz) // must check this equation. + removeCached(oldname); // see getAndRename(). + + String newName = clazz.getName(); + checkNotFrozen(newName); + cacheCtClass(newName, clazz, false); + } + + /** + * Reads a class file from the source and returns a reference + * to the <code>CtClass</code> + * object representing that class file. If that class file has been + * already read, this method returns a reference to the + * <code>CtClass</code> created when that class file was read at the + * first time. + * + * <p>If <code>classname</code> ends with "[]", then this method + * returns a <code>CtClass</code> object for that array type. + * + * <p>To obtain an inner class, use "$" instead of "." for separating + * the enclosing class name and the inner class name. + * + * @param classname a fully-qualified class name. + */ + public CtClass get(String classname) throws NotFoundException { + CtClass clazz; + if (classname == null) + clazz = null; + else + clazz = get0(classname, true); + + if (clazz == null) + throw new NotFoundException(classname); + else { + clazz.incGetCounter(); + return clazz; + } + } + + /** + * Reads a class file from the source and returns a reference + * to the <code>CtClass</code> + * object representing that class file. + * This method is equivalent to <code>get</code> except + * that it returns <code>null</code> when a class file is + * not found and it never throws an exception. + * + * @param classname a fully-qualified class name. + * @return a <code>CtClass</code> object or <code>null</code>. + * @see #get(String) + * @see #find(String) + * @since 3.13 + */ + public CtClass getOrNull(String classname) { + CtClass clazz = null; + if (classname == null) + clazz = null; + else + try { + /* ClassPool.get0() never throws an exception + but its subclass may implement get0 that + may throw an exception. + */ + clazz = get0(classname, true); + } + catch (NotFoundException e){} + + if (clazz != null) + clazz.incGetCounter(); + + return clazz; + } + + /** + * Returns a <code>CtClass</code> object with the given name. + * This is almost equivalent to <code>get(String)</code> except + * that classname can be an array-type "descriptor" (an encoded + * type name) such as <code>[Ljava/lang/Object;</code>. + * + * <p>Using this method is not recommended; this method should be + * used only to obtain the <code>CtClass</code> object + * with a name returned from <code>getClassInfo</code> in + * <code>javassist.bytecode.ClassPool</code>. <code>getClassInfo</code> + * returns a fully-qualified class name but, if the class is an array + * type, it returns a descriptor. + * + * @param classname a fully-qualified class name or a descriptor + * representing an array type. + * @see #get(String) + * @see javassist.bytecode.ConstPool#getClassInfo(int) + * @see javassist.bytecode.Descriptor#toCtClass(String, ClassPool) + * @since 3.8.1 + */ + public CtClass getCtClass(String classname) throws NotFoundException { + if (classname.charAt(0) == '[') + return Descriptor.toCtClass(classname, this); + else + return get(classname); + } + + /** + * @param useCache false if the cached CtClass must be ignored. + * @param searchParent false if the parent class pool is not searched. + * @return null if the class could not be found. + */ + protected synchronized CtClass get0(String classname, boolean useCache) + throws NotFoundException + { + CtClass clazz = null; + if (useCache) { + clazz = getCached(classname); + if (clazz != null) + return clazz; + } + + if (!childFirstLookup && parent != null) { + clazz = parent.get0(classname, useCache); + if (clazz != null) + return clazz; + } + + clazz = createCtClass(classname, useCache); + if (clazz != null) { + // clazz.getName() != classname if classname is "[L<name>;". + if (useCache) + cacheCtClass(clazz.getName(), clazz, false); + + return clazz; + } + + if (childFirstLookup && parent != null) + clazz = parent.get0(classname, useCache); + + return clazz; + } + + /** + * Creates a CtClass object representing the specified class. + * It first examines whether or not the corresponding class + * file exists. If yes, it creates a CtClass object. + * + * @return null if the class file could not be found. + */ + protected CtClass createCtClass(String classname, boolean useCache) { + // accept "[L<class name>;" as a class name. + if (classname.charAt(0) == '[') + classname = Descriptor.toClassName(classname); + + if (classname.endsWith("[]")) { + String base = classname.substring(0, classname.indexOf('[')); + if ((!useCache || getCached(base) == null) && find(base) == null) + return null; + else + return new CtArray(classname, this); + } + else + if (find(classname) == null) + return null; + else + return new CtClassType(classname, this); + } + + /** + * Searches the class path to obtain the URL of the class file + * specified by classname. It is also used to determine whether + * the class file exists. + * + * @param classname a fully-qualified class name. + * @return null if the class file could not be found. + * @see CtClass#getURL() + */ + public URL find(String classname) { + return source.find(classname); + } + + /* + * Is invoked by CtClassType.setName() and methods in this class. + * This method throws an exception if the class is already frozen or + * if this class pool cannot edit the class since it is in a parent + * class pool. + * + * @see checkNotExists(String) + */ + void checkNotFrozen(String classname) throws RuntimeException { + CtClass clazz = getCached(classname); + if (clazz == null) { + if (!childFirstLookup && parent != null) { + try { + clazz = parent.get0(classname, true); + } + catch (NotFoundException e) {} + if (clazz != null) + throw new RuntimeException(classname + + " is in a parent ClassPool. Use the parent."); + } + } + else + if (clazz.isFrozen()) + throw new RuntimeException(classname + + ": frozen class (cannot edit)"); + } + + /* + * This method returns null if this or its parent class pool does + * not contain a CtClass object with the class name. + * + * @see checkNotFrozen(String) + */ + CtClass checkNotExists(String classname) { + CtClass clazz = getCached(classname); + if (clazz == null) + if (!childFirstLookup && parent != null) { + try { + clazz = parent.get0(classname, true); + } + catch (NotFoundException e) {} + } + + return clazz; + } + + /* for CtClassType.getClassFile2(). Don't delegate to the parent. + */ + InputStream openClassfile(String classname) throws NotFoundException { + return source.openClassfile(classname); + } + + void writeClassfile(String classname, OutputStream out) + throws NotFoundException, IOException, CannotCompileException + { + source.writeClassfile(classname, out); + } + + /** + * Reads class files from the source and returns an array of + * <code>CtClass</code> + * objects representing those class files. + * + * <p>If an element of <code>classnames</code> ends with "[]", + * then this method + * returns a <code>CtClass</code> object for that array type. + * + * @param classnames an array of fully-qualified class name. + */ + public CtClass[] get(String[] classnames) throws NotFoundException { + if (classnames == null) + return new CtClass[0]; + + int num = classnames.length; + CtClass[] result = new CtClass[num]; + for (int i = 0; i < num; ++i) + result[i] = get(classnames[i]); + + return result; + } + + /** + * Reads a class file and obtains a compile-time method. + * + * @param classname the class name + * @param methodname the method name + * @see CtClass#getDeclaredMethod(String) + */ + public CtMethod getMethod(String classname, String methodname) + throws NotFoundException + { + CtClass c = get(classname); + return c.getDeclaredMethod(methodname); + } + + /** + * Creates a new class (or interface) from the given class file. + * If there already exists a class with the same name, the new class + * overwrites that previous class. + * + * <p>This method is used for creating a <code>CtClass</code> object + * directly from a class file. The qualified class name is obtained + * from the class file; you do not have to explicitly give the name. + * + * @param classfile class file. + * @throws RuntimeException if there is a frozen class with the + * the same name. + * @see #makeClassIfNew(InputStream) + * @see javassist.ByteArrayClassPath + */ + public CtClass makeClass(InputStream classfile) + throws IOException, RuntimeException + { + return makeClass(classfile, true); + } + + /** + * Creates a new class (or interface) from the given class file. + * If there already exists a class with the same name, the new class + * overwrites that previous class. + * + * <p>This method is used for creating a <code>CtClass</code> object + * directly from a class file. The qualified class name is obtained + * from the class file; you do not have to explicitly give the name. + * + * @param classfile class file. + * @param ifNotFrozen throws a RuntimeException if this parameter is true + * and there is a frozen class with the same name. + * @see javassist.ByteArrayClassPath + */ + public CtClass makeClass(InputStream classfile, boolean ifNotFrozen) + throws IOException, RuntimeException + { + compress(); + classfile = new BufferedInputStream(classfile); + CtClass clazz = new CtClassType(classfile, this); + clazz.checkModify(); + String classname = clazz.getName(); + if (ifNotFrozen) + checkNotFrozen(classname); + + cacheCtClass(classname, clazz, true); + return clazz; + } + + /** + * Creates a new class (or interface) from the given class file. + * If there already exists a class with the same name, this method + * returns the existing class; a new class is never created from + * the given class file. + * + * <p>This method is used for creating a <code>CtClass</code> object + * directly from a class file. The qualified class name is obtained + * from the class file; you do not have to explicitly give the name. + * + * @param classfile the class file. + * @see #makeClass(InputStream) + * @see javassist.ByteArrayClassPath + * @since 3.9 + */ + public CtClass makeClassIfNew(InputStream classfile) + throws IOException, RuntimeException + { + compress(); + classfile = new BufferedInputStream(classfile); + CtClass clazz = new CtClassType(classfile, this); + clazz.checkModify(); + String classname = clazz.getName(); + CtClass found = checkNotExists(classname); + if (found != null) + return found; + else { + cacheCtClass(classname, clazz, true); + return clazz; + } + } + + /** + * Creates a new public class. + * If there already exists a class with the same name, the new class + * overwrites that previous class. + * + * <p>If no constructor is explicitly added to the created new + * class, Javassist generates constructors and adds it when + * the class file is generated. It generates a new constructor + * for each constructor of the super class. The new constructor + * takes the same set of parameters and invokes the + * corresponding constructor of the super class. All the received + * parameters are passed to it. + * + * @param classname a fully-qualified class name. + * @throws RuntimeException if the existing class is frozen. + */ + public CtClass makeClass(String classname) throws RuntimeException { + return makeClass(classname, null); + } + + /** + * Creates a new public class. + * If there already exists a class/interface with the same name, + * the new class overwrites that previous class. + * + * <p>If no constructor is explicitly added to the created new + * class, Javassist generates constructors and adds it when + * the class file is generated. It generates a new constructor + * for each constructor of the super class. The new constructor + * takes the same set of parameters and invokes the + * corresponding constructor of the super class. All the received + * parameters are passed to it. + * + * @param classname a fully-qualified class name. + * @param superclass the super class. + * @throws RuntimeException if the existing class is frozen. + */ + public synchronized CtClass makeClass(String classname, CtClass superclass) + throws RuntimeException + { + checkNotFrozen(classname); + CtClass clazz = new CtNewClass(classname, this, false, superclass); + cacheCtClass(classname, clazz, true); + return clazz; + } + + /** + * Creates a new public nested class. + * This method is called by CtClassType.makeNestedClass(). + * + * @param classname a fully-qualified class name. + * @return the nested class. + */ + synchronized CtClass makeNestedClass(String classname) { + checkNotFrozen(classname); + CtClass clazz = new CtNewNestedClass(classname, this, false, null); + cacheCtClass(classname, clazz, true); + return clazz; + } + + /** + * Creates a new public interface. + * If there already exists a class/interface with the same name, + * the new interface overwrites that previous one. + * + * @param name a fully-qualified interface name. + * @throws RuntimeException if the existing interface is frozen. + */ + public CtClass makeInterface(String name) throws RuntimeException { + return makeInterface(name, null); + } + + /** + * Creates a new public interface. + * If there already exists a class/interface with the same name, + * the new interface overwrites that previous one. + * + * @param name a fully-qualified interface name. + * @param superclass the super interface. + * @throws RuntimeException if the existing interface is frozen. + */ + public synchronized CtClass makeInterface(String name, CtClass superclass) + throws RuntimeException + { + checkNotFrozen(name); + CtClass clazz = new CtNewClass(name, this, true, superclass); + cacheCtClass(name, clazz, true); + return clazz; + } + + /** + * Appends the system search path to the end of the + * search path. The system search path + * usually includes the platform library, extension + * libraries, and the search path specified by the + * <code>-classpath</code> option or the <code>CLASSPATH</code> + * environment variable. + * + * @return the appended class path. + */ + public ClassPath appendSystemPath() { + return source.appendSystemPath(); + } + + /** + * Insert a <code>ClassPath</code> object at the head of the + * search path. + * + * @return the inserted class path. + * @see javassist.ClassPath + * @see javassist.URLClassPath + * @see javassist.ByteArrayClassPath + */ + public ClassPath insertClassPath(ClassPath cp) { + return source.insertClassPath(cp); + } + + /** + * Appends a <code>ClassPath</code> object to the end of the + * search path. + * + * @return the appended class path. + * @see javassist.ClassPath + * @see javassist.URLClassPath + * @see javassist.ByteArrayClassPath + */ + public ClassPath appendClassPath(ClassPath cp) { + return source.appendClassPath(cp); + } + + /** + * Inserts a directory or a jar (or zip) file at the head of the + * search path. + * + * @param pathname the path name of the directory or jar file. + * It must not end with a path separator ("/"). + * If the path name ends with "/*", then all the + * jar files matching the path name are inserted. + * + * @return the inserted class path. + * @throws NotFoundException if the jar file is not found. + */ + public ClassPath insertClassPath(String pathname) + throws NotFoundException + { + return source.insertClassPath(pathname); + } + + /** + * Appends a directory or a jar (or zip) file to the end of the + * search path. + * + * @param pathname the path name of the directory or jar file. + * It must not end with a path separator ("/"). + * If the path name ends with "/*", then all the + * jar files matching the path name are appended. + * + * @return the appended class path. + * @throws NotFoundException if the jar file is not found. + */ + public ClassPath appendClassPath(String pathname) + throws NotFoundException + { + return source.appendClassPath(pathname); + } + + /** + * Detatches the <code>ClassPath</code> object from the search path. + * The detached <code>ClassPath</code> object cannot be added + * to the pathagain. + */ + public void removeClassPath(ClassPath cp) { + source.removeClassPath(cp); + } + + /** + * Appends directories and jar files for search. + * + * <p>The elements of the given path list must be separated by colons + * in Unix or semi-colons in Windows. + * + * @param pathlist a (semi)colon-separated list of + * the path names of directories and jar files. + * The directory name must not end with a path + * separator ("/"). + * @throws NotFoundException if a jar file is not found. + */ + public void appendPathList(String pathlist) throws NotFoundException { + char sep = File.pathSeparatorChar; + int i = 0; + for (;;) { + int j = pathlist.indexOf(sep, i); + if (j < 0) { + appendClassPath(pathlist.substring(i)); + break; + } + else { + appendClassPath(pathlist.substring(i, j)); + i = j + 1; + } + } + } + + /** + * Converts the given class to a <code>java.lang.Class</code> object. + * Once this method is called, further modifications are not + * allowed any more. + * To load the class, this method uses the context class loader + * of the current thread. It is obtained by calling + * <code>getClassLoader()</code>. + * + * <p>This behavior can be changed by subclassing the pool and changing + * the <code>getClassLoader()</code> method. + * If the program is running on some application + * server, the context class loader might be inappropriate to load the + * class. + * + * <p>This method is provided for convenience. If you need more + * complex functionality, you should write your own class loader. + * + * <p><b>Warining:</b> A Class object returned by this method may not + * work with a security manager or a signed jar file because a + * protection domain is not specified. + * + * @see #toClass(CtClass, java.lang.ClassLoader, ProtectionDomain) + * @see #getClassLoader() + */ + public Class toClass(CtClass clazz) throws CannotCompileException { + // Some subclasses of ClassPool may override toClass(CtClass,ClassLoader). + // So we should call that method instead of toClass(.., ProtectionDomain). + return toClass(clazz, getClassLoader()); + } + + /** + * Get the classloader for <code>toClass()</code>, <code>getAnnotations()</code> in + * <code>CtClass</code>, etc. + * + * <p>The default is the context class loader. + * + * @return the classloader for the pool + * @see #toClass(CtClass) + * @see CtClass#getAnnotations() + */ + public ClassLoader getClassLoader() { + return getContextClassLoader(); + } + + /** + * Obtains a class loader that seems appropriate to look up a class + * by name. + */ + static ClassLoader getContextClassLoader() { + return Thread.currentThread().getContextClassLoader(); + } + + /** + * Converts the class to a <code>java.lang.Class</code> object. + * Do not override this method any more at a subclass because + * <code>toClass(CtClass)</code> never calls this method. + * + * <p><b>Warining:</b> A Class object returned by this method may not + * work with a security manager or a signed jar file because a + * protection domain is not specified. + * + * @deprecated Replaced by {@link #toClass(CtClass,ClassLoader,ProtectionDomain)}. + * A subclass of <code>ClassPool</code> that has been + * overriding this method should be modified. It should override + * {@link #toClass(CtClass,ClassLoader,ProtectionDomain)}. + */ + public Class toClass(CtClass ct, ClassLoader loader) + throws CannotCompileException + { + return toClass(ct, loader, null); + } + + /** + * Converts the class to a <code>java.lang.Class</code> object. + * Once this method is called, further modifications are not allowed + * any more. + * + * <p>The class file represented by the given <code>CtClass</code> is + * loaded by the given class loader to construct a + * <code>java.lang.Class</code> object. Since a private method + * on the class loader is invoked through the reflection API, + * the caller must have permissions to do that. + * + * <p>An easy way to obtain <code>ProtectionDomain</code> object is + * to call <code>getProtectionDomain()</code> + * in <code>java.lang.Class</code>. It returns the domain that the + * class belongs to. + * + * <p>This method is provided for convenience. If you need more + * complex functionality, you should write your own class loader. + * + * @param loader the class loader used to load this class. + * For example, the loader returned by + * <code>getClassLoader()</code> can be used + * for this parameter. + * @param domain the protection domain for the class. + * If it is null, the default domain created + * by <code>java.lang.ClassLoader</code> is used. + * + * @see #getClassLoader() + * @since 3.3 + */ + public Class toClass(CtClass ct, ClassLoader loader, ProtectionDomain domain) + throws CannotCompileException + { + try { + byte[] b = ct.toBytecode(); + java.lang.reflect.Method method; + Object[] args; + if (domain == null) { + method = defineClass1; + args = new Object[] { ct.getName(), b, new Integer(0), + new Integer(b.length)}; + } + else { + method = defineClass2; + args = new Object[] { ct.getName(), b, new Integer(0), + new Integer(b.length), domain}; + } + + return toClass2(method, loader, args); + } + catch (RuntimeException e) { + throw e; + } + catch (java.lang.reflect.InvocationTargetException e) { + throw new CannotCompileException(e.getTargetException()); + } + catch (Exception e) { + throw new CannotCompileException(e); + } + } + + private static synchronized Class toClass2(Method method, + ClassLoader loader, Object[] args) + throws Exception + { + method.setAccessible(true); + try { + return (Class)method.invoke(loader, args); + } + finally { + method.setAccessible(false); + } + } +} diff --git a/src/main/javassist/ClassPoolTail.java b/src/main/javassist/ClassPoolTail.java new file mode 100644 index 0000000..aa1aefe --- /dev/null +++ b/src/main/javassist/ClassPoolTail.java @@ -0,0 +1,442 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist; + +import java.io.*; +import java.util.jar.*; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Hashtable; + +final class ClassPathList { + ClassPathList next; + ClassPath path; + + ClassPathList(ClassPath p, ClassPathList n) { + next = n; + path = p; + } +} + +final class DirClassPath implements ClassPath { + String directory; + + DirClassPath(String dirName) { + directory = dirName; + } + + public InputStream openClassfile(String classname) { + try { + char sep = File.separatorChar; + String filename = directory + sep + + classname.replace('.', sep) + ".class"; + return new FileInputStream(filename.toString()); + } + catch (FileNotFoundException e) {} + catch (SecurityException e) {} + return null; + } + + public URL find(String classname) { + char sep = File.separatorChar; + String filename = directory + sep + + classname.replace('.', sep) + ".class"; + File f = new File(filename); + if (f.exists()) + try { + return f.getCanonicalFile().toURI().toURL(); + } + catch (MalformedURLException e) {} + catch (IOException e) {} + + return null; + } + + public void close() {} + + public String toString() { + return directory; + } +} + +final class JarDirClassPath implements ClassPath { + JarClassPath[] jars; + + JarDirClassPath(String dirName) throws NotFoundException { + File[] files = new File(dirName).listFiles(new FilenameFilter() { + public boolean accept(File dir, String name) { + name = name.toLowerCase(); + return name.endsWith(".jar") || name.endsWith(".zip"); + } + }); + + if (files != null) { + jars = new JarClassPath[files.length]; + for (int i = 0; i < files.length; i++) + jars[i] = new JarClassPath(files[i].getPath()); + } + } + + public InputStream openClassfile(String classname) throws NotFoundException { + if (jars != null) + for (int i = 0; i < jars.length; i++) { + InputStream is = jars[i].openClassfile(classname); + if (is != null) + return is; + } + + return null; // not found + } + + public URL find(String classname) { + if (jars != null) + for (int i = 0; i < jars.length; i++) { + URL url = jars[i].find(classname); + if (url != null) + return url; + } + + return null; // not found + } + + public void close() { + if (jars != null) + for (int i = 0; i < jars.length; i++) + jars[i].close(); + } +} + +final class JarClassPath implements ClassPath { + JarFile jarfile; + String jarfileURL; + + JarClassPath(String pathname) throws NotFoundException { + try { + jarfile = new JarFile(pathname); + jarfileURL = new File(pathname).getCanonicalFile() + .toURI().toURL().toString(); + return; + } + catch (IOException e) {} + throw new NotFoundException(pathname); + } + + public InputStream openClassfile(String classname) + throws NotFoundException + { + try { + String jarname = classname.replace('.', '/') + ".class"; + JarEntry je = jarfile.getJarEntry(jarname); + if (je != null) + return jarfile.getInputStream(je); + else + return null; // not found + } + catch (IOException e) {} + throw new NotFoundException("broken jar file?: " + + jarfile.getName()); + } + + public URL find(String classname) { + String jarname = classname.replace('.', '/') + ".class"; + JarEntry je = jarfile.getJarEntry(jarname); + if (je != null) + try { + return new URL("jar:" + jarfileURL + "!/" + jarname); + } + catch (MalformedURLException e) {} + + return null; // not found + } + + public void close() { + try { + jarfile.close(); + jarfile = null; + } + catch (IOException e) {} + } + + public String toString() { + return jarfile == null ? "<null>" : jarfile.toString(); + } +} + +final class ClassPoolTail { + protected ClassPathList pathList; + private Hashtable packages; // should be synchronized. + + public ClassPoolTail() { + pathList = null; + packages = new Hashtable(); + } + + public String toString() { + StringBuffer buf = new StringBuffer(); + buf.append("[class path: "); + ClassPathList list = pathList; + while (list != null) { + buf.append(list.path.toString()); + buf.append(File.pathSeparatorChar); + list = list.next; + } + + buf.append(']'); + return buf.toString(); + } + + public synchronized ClassPath insertClassPath(ClassPath cp) { + pathList = new ClassPathList(cp, pathList); + return cp; + } + + public synchronized ClassPath appendClassPath(ClassPath cp) { + ClassPathList tail = new ClassPathList(cp, null); + ClassPathList list = pathList; + if (list == null) + pathList = tail; + else { + while (list.next != null) + list = list.next; + + list.next = tail; + } + + return cp; + } + + public synchronized void removeClassPath(ClassPath cp) { + ClassPathList list = pathList; + if (list != null) + if (list.path == cp) + pathList = list.next; + else { + while (list.next != null) + if (list.next.path == cp) + list.next = list.next.next; + else + list = list.next; + } + + cp.close(); + } + + public ClassPath appendSystemPath() { + return appendClassPath(new ClassClassPath()); + } + + public ClassPath insertClassPath(String pathname) + throws NotFoundException + { + return insertClassPath(makePathObject(pathname)); + } + + public ClassPath appendClassPath(String pathname) + throws NotFoundException + { + return appendClassPath(makePathObject(pathname)); + } + + private static ClassPath makePathObject(String pathname) + throws NotFoundException + { + String lower = pathname.toLowerCase(); + if (lower.endsWith(".jar") || lower.endsWith(".zip")) + return new JarClassPath(pathname); + + int len = pathname.length(); + if (len > 2 && pathname.charAt(len - 1) == '*' + && (pathname.charAt(len - 2) == '/' + || pathname.charAt(len - 2) == File.separatorChar)) { + String dir = pathname.substring(0, len - 2); + return new JarDirClassPath(dir); + } + + return new DirClassPath(pathname); + } + + /** + * You can record "System" so that java.lang.System can be quickly + * found although "System" is not a package name. + */ + public void recordInvalidClassName(String name) { + packages.put(name, name); + } + + /** + * This method does not close the output stream. + */ + void writeClassfile(String classname, OutputStream out) + throws NotFoundException, IOException, CannotCompileException + { + InputStream fin = openClassfile(classname); + if (fin == null) + throw new NotFoundException(classname); + + try { + copyStream(fin, out); + } + finally { + fin.close(); + } + } + + /* + -- faster version -- + void checkClassName(String classname) throws NotFoundException { + if (find(classname) == null) + throw new NotFoundException(classname); + } + + -- slower version -- + + void checkClassName(String classname) throws NotFoundException { + InputStream fin = openClassfile(classname); + try { + fin.close(); + } + catch (IOException e) {} + } + */ + + + /** + * Opens the class file for the class specified by + * <code>classname</code>. + * + * @param classname a fully-qualified class name + * @return null if the file has not been found. + * @throws NotFoundException if any error is reported by ClassPath. + */ + InputStream openClassfile(String classname) + throws NotFoundException + { + if (packages.get(classname) != null) + return null; // not found + + ClassPathList list = pathList; + InputStream ins = null; + NotFoundException error = null; + while (list != null) { + try { + ins = list.path.openClassfile(classname); + } + catch (NotFoundException e) { + if (error == null) + error = e; + } + + if (ins == null) + list = list.next; + else + return ins; + } + + if (error != null) + throw error; + else + return null; // not found + } + + /** + * Searches the class path to obtain the URL of the class file + * specified by classname. It is also used to determine whether + * the class file exists. + * + * @param classname a fully-qualified class name. + * @return null if the class file could not be found. + */ + public URL find(String classname) { + if (packages.get(classname) != null) + return null; + + ClassPathList list = pathList; + URL url = null; + while (list != null) { + url = list.path.find(classname); + if (url == null) + list = list.next; + else + return url; + } + + return null; + } + + /** + * Reads from an input stream until it reaches the end. + * + * @return the contents of that input stream + */ + public static byte[] readStream(InputStream fin) throws IOException { + byte[][] bufs = new byte[8][]; + int bufsize = 4096; + + for (int i = 0; i < 8; ++i) { + bufs[i] = new byte[bufsize]; + int size = 0; + int len = 0; + do { + len = fin.read(bufs[i], size, bufsize - size); + if (len >= 0) + size += len; + else { + byte[] result = new byte[bufsize - 4096 + size]; + int s = 0; + for (int j = 0; j < i; ++j) { + System.arraycopy(bufs[j], 0, result, s, s + 4096); + s = s + s + 4096; + } + + System.arraycopy(bufs[i], 0, result, s, size); + return result; + } + } while (size < bufsize); + bufsize *= 2; + } + + throw new IOException("too much data"); + } + + /** + * Reads from an input stream and write to an output stream + * until it reaches the end. This method does not close the + * streams. + */ + public static void copyStream(InputStream fin, OutputStream fout) + throws IOException + { + int bufsize = 4096; + for (int i = 0; i < 8; ++i) { + byte[] buf = new byte[bufsize]; + int size = 0; + int len = 0; + do { + len = fin.read(buf, size, bufsize - size); + if (len >= 0) + size += len; + else { + fout.write(buf, 0, size); + return; + } + } while (size < bufsize); + fout.write(buf); + bufsize *= 2; + } + + throw new IOException("too much data"); + } +} diff --git a/src/main/javassist/CodeConverter.java b/src/main/javassist/CodeConverter.java new file mode 100644 index 0000000..44881d5 --- /dev/null +++ b/src/main/javassist/CodeConverter.java @@ -0,0 +1,799 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist; + +import javassist.bytecode.*; +import javassist.convert.*; + +/** + * Simple translator of method bodies + * (also see the <code>javassist.expr</code> package). + * + * <p>Instances of this class specifies how to instrument of the + * bytecodes representing a method body. They are passed to + * <code>CtClass.instrument()</code> or + * <code>CtMethod.instrument()</code> as a parameter. + * + * <p>Example: + * <ul><pre> + * ClassPool cp = ClassPool.getDefault(); + * CtClass point = cp.get("Point"); + * CtClass singleton = cp.get("Singleton"); + * CtClass client = cp.get("Client"); + * CodeConverter conv = new CodeConverter(); + * conv.replaceNew(point, singleton, "makePoint"); + * client.instrument(conv); + * </pre></ul> + * + * <p>This program substitutes "<code>Singleton.makePoint()</code>" + * for all occurrences of "<code>new Point()</code>" + * appearing in methods declared in a <code>Client</code> class. + * + * @see javassist.CtClass#instrument(CodeConverter) + * @see javassist.CtMethod#instrument(CodeConverter) + * @see javassist.expr.ExprEditor + */ +public class CodeConverter { + protected Transformer transformers = null; + + /** + * Modify a method body so that instantiation of the specified class + * is replaced with a call to the specified static method. For example, + * <code>replaceNew(ctPoint, ctSingleton, "createPoint")</code> + * (where <code>ctPoint</code> and <code>ctSingleton</code> are + * compile-time classes for class <code>Point</code> and class + * <code>Singleton</code>, respectively) + * replaces all occurrences of: + * + * <ul><code>new Point(x, y)</code></ul> + * + * in the method body with: + * + * <ul><code>Singleton.createPoint(x, y)</code></ul> + * + * <p>This enables to intercept instantiation of <code>Point</code> + * and change the samentics. For example, the following + * <code>createPoint()</code> implements the singleton pattern: + * + * <ul><pre>public static Point createPoint(int x, int y) { + * if (aPoint == null) + * aPoint = new Point(x, y); + * return aPoint; + * } + * </pre></ul> + * + * <p>The static method call substituted for the original <code>new</code> + * expression must be + * able to receive the same set of parameters as the original + * constructor. If there are multiple constructors with different + * parameter types, then there must be multiple static methods + * with the same name but different parameter types. + * + * <p>The return type of the substituted static method must be + * the exactly same as the type of the instantiated class specified by + * <code>newClass</code>. + * + * @param newClass the instantiated class. + * @param calledClass the class in which the static method is + * declared. + * @param calledMethod the name of the static method. + */ + public void replaceNew(CtClass newClass, + CtClass calledClass, String calledMethod) { + transformers = new TransformNew(transformers, newClass.getName(), + calledClass.getName(), calledMethod); + } + + /** + * Modify a method body so that instantiation of the class + * specified by <code>oldClass</code> + * is replaced with instantiation of another class <code>newClass</code>. + * For example, + * <code>replaceNew(ctPoint, ctPoint2)</code> + * (where <code>ctPoint</code> and <code>ctPoint2</code> are + * compile-time classes for class <code>Point</code> and class + * <code>Point2</code>, respectively) + * replaces all occurrences of: + * + * <ul><code>new Point(x, y)</code></ul> + * + * in the method body with: + * + * <ul><code>new Point2(x, y)</code></ul> + * + * <p>Note that <code>Point2</code> must be type-compatible with <code>Point</code>. + * It must have the same set of methods, fields, and constructors as the + * replaced class. + */ + public void replaceNew(CtClass oldClass, CtClass newClass) { + transformers = new TransformNewClass(transformers, oldClass.getName(), + newClass.getName()); + } + + /** + * Modify a method body so that field read/write expressions access + * a different field from the original one. + * + * <p>Note that this method changes only the filed name and the class + * declaring the field; the type of the target object does not change. + * Therefore, the substituted field must be declared in the same class + * or a superclass of the original class. + * + * <p>Also, <code>clazz</code> and <code>newClass</code> must specify + * the class directly declaring the field. They must not specify + * a subclass of that class. + * + * @param field the originally accessed field. + * @param newClass the class declaring the substituted field. + * @param newFieldname the name of the substituted field. + */ + public void redirectFieldAccess(CtField field, + CtClass newClass, String newFieldname) { + transformers = new TransformFieldAccess(transformers, field, + newClass.getName(), + newFieldname); + } + + /** + * Modify a method body so that an expression reading the specified + * field is replaced with a call to the specified <i>static</i> method. + * This static method receives the target object of the original + * read expression as a parameter. It must return a value of + * the same type as the field. + * + * <p>For example, the program below + * + * <ul><pre>Point p = new Point(); + * int newX = p.x + 3;</pre></ul> + * + * <p>can be translated into: + * + * <ul><pre>Point p = new Point(); + * int newX = Accessor.readX(p) + 3;</pre></ul> + * + * <p>where + * + * <ul><pre>public class Accessor { + * public static int readX(Object target) { ... } + * }</pre></ul> + * + * <p>The type of the parameter of <code>readX()</code> must + * be <code>java.lang.Object</code> independently of the actual + * type of <code>target</code>. The return type must be the same + * as the field type. + * + * @param field the field. + * @param calledClass the class in which the static method is + * declared. + * @param calledMethod the name of the static method. + */ + public void replaceFieldRead(CtField field, + CtClass calledClass, String calledMethod) { + transformers = new TransformReadField(transformers, field, + calledClass.getName(), + calledMethod); + } + + /** + * Modify a method body so that an expression writing the specified + * field is replaced with a call to the specified static method. + * This static method receives two parameters: the target object of + * the original + * write expression and the assigned value. The return type of the + * static method is <code>void</code>. + * + * <p>For example, the program below + * + * <ul><pre>Point p = new Point(); + * p.x = 3;</pre></ul> + * + * <p>can be translated into: + * + * <ul><pre>Point p = new Point(); + * Accessor.writeX(3);</pre></ul> + * + * <p>where + * + * <ul><pre>public class Accessor { + * public static void writeX(Object target, int value) { ... } + * }</pre></ul> + * + * <p>The type of the first parameter of <code>writeX()</code> must + * be <code>java.lang.Object</code> independently of the actual + * type of <code>target</code>. The type of the second parameter + * is the same as the field type. + * + * @param field the field. + * @param calledClass the class in which the static method is + * declared. + * @param calledMethod the name of the static method. + */ + public void replaceFieldWrite(CtField field, + CtClass calledClass, String calledMethod) { + transformers = new TransformWriteField(transformers, field, + calledClass.getName(), + calledMethod); + } + + /** + * Modify a method body, so that ALL accesses to an array are replaced with + * calls to static methods within another class. In the case of reading an + * element from the array, this is replaced with a call to a static method with + * the array and the index as arguments, the return value is the value read from + * the array. If writing to an array, this is replaced with a call to a static + * method with the array, index and new value as parameters, the return value of + * the static method is <code>void</code>. + * + * <p>The <code>calledClass</code> parameter is the class containing the static methods to be used + * for array replacement. The <code>names</code> parameter points to an implementation of + * <code>ArrayAccessReplacementMethodNames</code> which specifies the names of the method to be + * used for access for each type of array. For example reading from an <code>int[]</code> will + * require a different method than if writing to an <code>int[]</code>, and writing to a <code>long[]</code> + * will require a different method than if writing to a <code>byte[]</code>. If the implementation + * of <code>ArrayAccessReplacementMethodNames</code> does not contain the name for access for a + * type of array, that access is not replaced. + * + * <p>A default implementation of <code>ArrayAccessReplacementMethodNames</code> called + * <code>DefaultArrayAccessReplacementMethodNames</code> has been provided and is what is used in the + * following example. This also assumes that <code>'foo.ArrayAdvisor'</code> is the name of the + * <code>CtClass</code> passed in. + * + * <p>If we have the following class: + * <pre>class POJO{ + * int[] ints = new int[]{1, 2, 3, 4, 5}; + * long[] longs = new int[]{10, 20, 30}; + * Object objects = new Object[]{true, false}; + * Integer[] integers = new Integer[]{new Integer(10)}; + * } + * </pre> + * and this is accessed as: + * <pre>POJO p = new POJO(); + * + * //Write to int array + * p.ints[2] = 7; + * + * //Read from int array + * int i = p.ints[2]; + * + * //Write to long array + * p.longs[2] = 1000L; + * + * //Read from long array + * long l = p.longs[2]; + * + * //Write to Object array + * p.objects[2] = "Hello"; + * + * //Read from Object array + * Object o = p.objects[2]; + * + * //Write to Integer array + * Integer integer = new Integer(5); + * p.integers[0] = integer; + * + * //Read from Object array + * integer = p.integers[0]; + * </pre> + * + * Following instrumentation we will have + * <pre>POJO p = new POJO(); + * + * //Write to int array + * ArrayAdvisor.arrayWriteInt(p.ints, 2, 7); + * + * //Read from int array + * int i = ArrayAdvisor.arrayReadInt(p.ints, 2); + * + * //Write to long array + * ArrayAdvisor.arrayWriteLong(p.longs, 2, 1000L); + * + * //Read from long array + * long l = ArrayAdvisor.arrayReadLong(p.longs, 2); + * + * //Write to Object array + * ArrayAdvisor.arrayWriteObject(p.objects, 2, "Hello"); + * + * //Read from Object array + * Object o = ArrayAdvisor.arrayReadObject(p.objects, 2); + * + * //Write to Integer array + * Integer integer = new Integer(5); + * ArrayAdvisor.arrayWriteObject(p.integers, 0, integer); + * + * //Read from Object array + * integer = ArrayAdvisor.arrayWriteObject(p.integers, 0); + * </pre> + * + * @see DefaultArrayAccessReplacementMethodNames + * + * @param calledClass the class containing the static methods. + * @param names contains the names of the methods to replace + * the different kinds of array access with. + */ + public void replaceArrayAccess(CtClass calledClass, ArrayAccessReplacementMethodNames names) + throws NotFoundException + { + transformers = new TransformAccessArrayField(transformers, calledClass.getName(), names); + } + + /** + * Modify method invocations in a method body so that a different + * method will be invoked. + * + * <p>Note that the target object, the parameters, or + * the type of invocation + * (static method call, interface call, or private method call) + * are not modified. Only the method name is changed. The substituted + * method must have the same signature that the original one has. + * If the original method is a static method, the substituted method + * must be static. + * + * @param origMethod original method + * @param substMethod substituted method + */ + public void redirectMethodCall(CtMethod origMethod, + CtMethod substMethod) + throws CannotCompileException + { + String d1 = origMethod.getMethodInfo2().getDescriptor(); + String d2 = substMethod.getMethodInfo2().getDescriptor(); + if (!d1.equals(d2)) + throw new CannotCompileException("signature mismatch: " + + substMethod.getLongName()); + + int mod1 = origMethod.getModifiers(); + int mod2 = substMethod.getModifiers(); + if (Modifier.isStatic(mod1) != Modifier.isStatic(mod2) + || (Modifier.isPrivate(mod1) && !Modifier.isPrivate(mod2)) + || origMethod.getDeclaringClass().isInterface() + != substMethod.getDeclaringClass().isInterface()) + throw new CannotCompileException("invoke-type mismatch " + + substMethod.getLongName()); + + transformers = new TransformCall(transformers, origMethod, + substMethod); + } + + /** + * Correct invocations to a method that has been renamed. + * If a method is renamed, calls to that method must be also + * modified so that the method with the new name will be called. + * + * <p>The method must be declared in the same class before and + * after it is renamed. + * + * <p>Note that the target object, the parameters, or + * the type of invocation + * (static method call, interface call, or private method call) + * are not modified. Only the method name is changed. + * + * @param oldMethodName the old name of the method. + * @param newMethod the method with the new name. + * @see javassist.CtMethod#setName(String) + */ + public void redirectMethodCall(String oldMethodName, + CtMethod newMethod) + throws CannotCompileException + { + transformers + = new TransformCall(transformers, oldMethodName, newMethod); + } + + /** + * Insert a call to another method before an existing method call. + * That "before" method must be static. The return type must be + * <code>void</code>. As parameters, the before method receives + * the target object and all the parameters to the originally invoked + * method. For example, if the originally invoked method is + * <code>move()</code>: + * + * <ul><pre>class Point { + * Point move(int x, int y) { ... } + * }</pre></ul> + * + * <p>Then the before method must be something like this: + * + * <ul><pre>class Verbose { + * static void print(Point target, int x, int y) { ... } + * }</pre></ul> + * + * <p>The <code>CodeConverter</code> would translate bytecode + * equivalent to: + * + * <ul><pre>Point p2 = p.move(x + y, 0);</pre></ul> + * + * <p>into the bytecode equivalent to: + * + * <ul><pre>int tmp1 = x + y; + * int tmp2 = 0; + * Verbose.print(p, tmp1, tmp2); + * Point p2 = p.move(tmp1, tmp2);</pre></ul> + * + * @param origMethod the method originally invoked. + * @param beforeMethod the method invoked before + * <code>origMethod</code>. + */ + public void insertBeforeMethod(CtMethod origMethod, + CtMethod beforeMethod) + throws CannotCompileException + { + try { + transformers = new TransformBefore(transformers, origMethod, + beforeMethod); + } + catch (NotFoundException e) { + throw new CannotCompileException(e); + } + } + + /** + * Inserts a call to another method after an existing method call. + * That "after" method must be static. The return type must be + * <code>void</code>. As parameters, the after method receives + * the target object and all the parameters to the originally invoked + * method. For example, if the originally invoked method is + * <code>move()</code>: + * + * <ul><pre>class Point { + * Point move(int x, int y) { ... } + * }</pre></ul> + * + * <p>Then the after method must be something like this: + * + * <ul><pre>class Verbose { + * static void print(Point target, int x, int y) { ... } + * }</pre></ul> + * + * <p>The <code>CodeConverter</code> would translate bytecode + * equivalent to: + * + * <ul><pre>Point p2 = p.move(x + y, 0);</pre></ul> + * + * <p>into the bytecode equivalent to: + * + * <ul><pre>int tmp1 = x + y; + * int tmp2 = 0; + * Point p2 = p.move(tmp1, tmp2); + * Verbose.print(p, tmp1, tmp2);</pre></ul> + * + * @param origMethod the method originally invoked. + * @param afterMethod the method invoked after + * <code>origMethod</code>. + */ + public void insertAfterMethod(CtMethod origMethod, + CtMethod afterMethod) + throws CannotCompileException + { + try { + transformers = new TransformAfter(transformers, origMethod, + afterMethod); + } + catch (NotFoundException e) { + throw new CannotCompileException(e); + } + } + + /** + * Performs code conversion. + */ + protected void doit(CtClass clazz, MethodInfo minfo, ConstPool cp) + throws CannotCompileException + { + Transformer t; + CodeAttribute codeAttr = minfo.getCodeAttribute(); + if (codeAttr == null || transformers == null) + return; + for (t = transformers; t != null; t = t.getNext()) + t.initialize(cp, clazz, minfo); + + CodeIterator iterator = codeAttr.iterator(); + while (iterator.hasNext()) { + try { + int pos = iterator.next(); + for (t = transformers; t != null; t = t.getNext()) + pos = t.transform(clazz, pos, iterator, cp); + } + catch (BadBytecode e) { + throw new CannotCompileException(e); + } + } + + int locals = 0; + int stack = 0; + for (t = transformers; t != null; t = t.getNext()) { + int s = t.extraLocals(); + if (s > locals) + locals = s; + + s = t.extraStack(); + if (s > stack) + stack = s; + } + + for (t = transformers; t != null; t = t.getNext()) + t.clean(); + + if (locals > 0) + codeAttr.setMaxLocals(codeAttr.getMaxLocals() + locals); + + if (stack > 0) + codeAttr.setMaxStack(codeAttr.getMaxStack() + stack); + } + + /** + * Interface containing the method names to be used + * as array access replacements. + * + * @author <a href="kabir.khan@jboss.com">Kabir Khan</a> + * @version $Revision: 1.16 $ + */ + public interface ArrayAccessReplacementMethodNames + { + /** + * Returns the name of a static method with the signature + * <code>(Ljava/lang/Object;I)B</code> to replace reading from a byte[]. + */ + String byteOrBooleanRead(); + + /** + * Returns the name of a static method with the signature + * <code>(Ljava/lang/Object;IB)V</code> to replace writing to a byte[]. + */ + String byteOrBooleanWrite(); + + /** + * @return the name of a static method with the signature + * <code>(Ljava/lang/Object;I)C</code> to replace reading from a char[]. + */ + String charRead(); + + /** + * Returns the name of a static method with the signature + * <code>(Ljava/lang/Object;IC)V</code> to replace writing to a byte[]. + */ + String charWrite(); + + /** + * Returns the name of a static method with the signature + * <code>(Ljava/lang/Object;I)D</code> to replace reading from a double[]. + */ + String doubleRead(); + + /** + * Returns the name of a static method with the signature + * <code>(Ljava/lang/Object;ID)V</code> to replace writing to a double[]. + */ + String doubleWrite(); + + /** + * Returns the name of a static method with the signature + * <code>(Ljava/lang/Object;I)F</code> to replace reading from a float[]. + */ + String floatRead(); + + /** + * Returns the name of a static method with the signature + * <code>(Ljava/lang/Object;IF)V</code> to replace writing to a float[]. + */ + String floatWrite(); + + /** + * Returns the name of a static method with the signature + * <code>(Ljava/lang/Object;I)I</code> to replace reading from a int[]. + */ + String intRead(); + + /** + * Returns the name of a static method with the signature + * <code>(Ljava/lang/Object;II)V</code> to replace writing to a int[]. + */ + String intWrite(); + + /** + * Returns the name of a static method with the signature + * <code>(Ljava/lang/Object;I)J</code> to replace reading from a long[]. + */ + String longRead(); + + /** + * Returns the name of a static method with the signature + * <code>(Ljava/lang/Object;IJ)V</code> to replace writing to a long[]. + */ + String longWrite(); + + /** + * Returns the name of a static method with the signature + * <code>(Ljava/lang/Object;I)Ljava/lang/Object;</code> + * to replace reading from a Object[] (or any subclass of object). + */ + String objectRead(); + + /** + * Returns the name of a static method with the signature + * <code>(Ljava/lang/Object;ILjava/lang/Object;)V</code> + * to replace writing to a Object[] (or any subclass of object). + */ + String objectWrite(); + + /** + * Returns the name of a static method with the signature + * <code>(Ljava/lang/Object;I)S</code> to replace reading from a short[]. + */ + String shortRead(); + + /** + * Returns the name of a static method with the signature + * <code>(Ljava/lang/Object;IS)V</code> to replace writing to a short[]. + */ + String shortWrite(); + } + + /** + * Default implementation of the <code>ArrayAccessReplacementMethodNames</code> + * interface giving default values for method names to be used for replacing + * accesses to array elements. + * + * @author <a href="kabir.khan@jboss.com">Kabir Khan</a> + * @version $Revision: 1.16 $ + */ + public static class DefaultArrayAccessReplacementMethodNames + implements ArrayAccessReplacementMethodNames + { + /** + * Returns "arrayReadByteOrBoolean" as the name of the static method with the signature + * (Ljava/lang/Object;I)B to replace reading from a byte[]. + */ + public String byteOrBooleanRead() + { + return "arrayReadByteOrBoolean"; + } + + /** + * Returns "arrayWriteByteOrBoolean" as the name of the static method with the signature + * (Ljava/lang/Object;IB)V to replace writing to a byte[]. + */ + public String byteOrBooleanWrite() + { + return "arrayWriteByteOrBoolean"; + } + + /** + * Returns "arrayReadChar" as the name of the static method with the signature + * (Ljava/lang/Object;I)C to replace reading from a char[]. + */ + public String charRead() + { + return "arrayReadChar"; + } + + /** + * Returns "arrayWriteChar" as the name of the static method with the signature + * (Ljava/lang/Object;IC)V to replace writing to a byte[]. + */ + public String charWrite() + { + return "arrayWriteChar"; + } + + /** + * Returns "arrayReadDouble" as the name of the static method with the signature + * (Ljava/lang/Object;I)D to replace reading from a double[]. + */ + public String doubleRead() + { + return "arrayReadDouble"; + } + + /** + * Returns "arrayWriteDouble" as the name of the static method with the signature + * (Ljava/lang/Object;ID)V to replace writing to a double[]. + */ + public String doubleWrite() + { + return "arrayWriteDouble"; + } + + /** + * Returns "arrayReadFloat" as the name of the static method with the signature + * (Ljava/lang/Object;I)F to replace reading from a float[]. + */ + public String floatRead() + { + return "arrayReadFloat"; + } + + /** + * Returns "arrayWriteFloat" as the name of the static method with the signature + * (Ljava/lang/Object;IF)V to replace writing to a float[]. + */ + public String floatWrite() + { + return "arrayWriteFloat"; + } + + /** + * Returns "arrayReadInt" as the name of the static method with the signature + * (Ljava/lang/Object;I)I to replace reading from a int[]. + */ + public String intRead() + { + return "arrayReadInt"; + } + + /** + * Returns "arrayWriteInt" as the name of the static method with the signature + * (Ljava/lang/Object;II)V to replace writing to a int[]. + */ + public String intWrite() + { + return "arrayWriteInt"; + } + + /** + * Returns "arrayReadLong" as the name of the static method with the signature + * (Ljava/lang/Object;I)J to replace reading from a long[]. + */ + public String longRead() + { + return "arrayReadLong"; + } + + /** + * Returns "arrayWriteLong" as the name of the static method with the signature + * (Ljava/lang/Object;IJ)V to replace writing to a long[]. + */ + public String longWrite() + { + return "arrayWriteLong"; + } + + /** + * Returns "arrayReadObject" as the name of the static method with the signature + * (Ljava/lang/Object;I)Ljava/lang/Object; to replace reading from a Object[] (or any subclass of object). + */ + public String objectRead() + { + return "arrayReadObject"; + } + + /** + * Returns "arrayWriteObject" as the name of the static method with the signature + * (Ljava/lang/Object;ILjava/lang/Object;)V to replace writing to a Object[] (or any subclass of object). + */ + public String objectWrite() + { + return "arrayWriteObject"; + } + + /** + * Returns "arrayReadShort" as the name of the static method with the signature + * (Ljava/lang/Object;I)S to replace reading from a short[]. + */ + public String shortRead() + { + return "arrayReadShort"; + } + + /** + * Returns "arrayWriteShort" as the name of the static method with the signature + * (Ljava/lang/Object;IS)V to replace writing to a short[]. + */ + public String shortWrite() + { + return "arrayWriteShort"; + } + } +} diff --git a/src/main/javassist/CtArray.java b/src/main/javassist/CtArray.java new file mode 100644 index 0000000..42fe609 --- /dev/null +++ b/src/main/javassist/CtArray.java @@ -0,0 +1,112 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist; + +/** + * Array types. + */ +final class CtArray extends CtClass { + protected ClassPool pool; + + // the name of array type ends with "[]". + CtArray(String name, ClassPool cp) { + super(name); + pool = cp; + } + + public ClassPool getClassPool() { + return pool; + } + + public boolean isArray() { + return true; + } + + private CtClass[] interfaces = null; + + public int getModifiers() { + int mod = Modifier.FINAL; + try { + mod |= getComponentType().getModifiers() + & (Modifier.PROTECTED | Modifier.PUBLIC | Modifier.PRIVATE); + } + catch (NotFoundException e) {} + return mod; + } + + public CtClass[] getInterfaces() throws NotFoundException { + if (interfaces == null) { + Class[] intfs = Object[].class.getInterfaces(); + // java.lang.Cloneable and java.io.Serializable. + // If the JVM is CLDC, intfs is empty. + interfaces = new CtClass[intfs.length]; + for (int i = 0; i < intfs.length; i++) + interfaces[i] = pool.get(intfs[i].getName()); + } + + return interfaces; + } + + public boolean subtypeOf(CtClass clazz) throws NotFoundException { + if (super.subtypeOf(clazz)) + return true; + + String cname = clazz.getName(); + if (cname.equals(javaLangObject)) + return true; + + CtClass[] intfs = getInterfaces(); + for (int i = 0; i < intfs.length; i++) + if (intfs[i].subtypeOf(clazz)) + return true; + + return clazz.isArray() + && getComponentType().subtypeOf(clazz.getComponentType()); + } + + public CtClass getComponentType() throws NotFoundException { + String name = getName(); + return pool.get(name.substring(0, name.length() - 2)); + } + + public CtClass getSuperclass() throws NotFoundException { + return pool.get(javaLangObject); + } + + public CtMethod[] getMethods() { + try { + return getSuperclass().getMethods(); + } + catch (NotFoundException e) { + return super.getMethods(); + } + } + + public CtMethod getMethod(String name, String desc) + throws NotFoundException + { + return getSuperclass().getMethod(name, desc); + } + + public CtConstructor[] getConstructors() { + try { + return getSuperclass().getConstructors(); + } + catch (NotFoundException e) { + return super.getConstructors(); + } + } +} diff --git a/src/main/javassist/CtBehavior.java b/src/main/javassist/CtBehavior.java new file mode 100644 index 0000000..1414d05 --- /dev/null +++ b/src/main/javassist/CtBehavior.java @@ -0,0 +1,1152 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist; + +import javassist.bytecode.*; +import javassist.compiler.Javac; +import javassist.compiler.CompileError; +import javassist.expr.ExprEditor; + +/** + * <code>CtBehavior</code> represents a method, a constructor, + * or a static constructor (class initializer). + * It is the abstract super class of + * <code>CtMethod</code> and <code>CtConstructor</code>. + */ +public abstract class CtBehavior extends CtMember { + protected MethodInfo methodInfo; + + protected CtBehavior(CtClass clazz, MethodInfo minfo) { + super(clazz); + methodInfo = minfo; + } + + /** + * @param isCons true if this is a constructor. + */ + void copy(CtBehavior src, boolean isCons, ClassMap map) + throws CannotCompileException + { + CtClass declaring = declaringClass; + MethodInfo srcInfo = src.methodInfo; + CtClass srcClass = src.getDeclaringClass(); + ConstPool cp = declaring.getClassFile2().getConstPool(); + + map = new ClassMap(map); + map.put(srcClass.getName(), declaring.getName()); + try { + boolean patch = false; + CtClass srcSuper = srcClass.getSuperclass(); + CtClass destSuper = declaring.getSuperclass(); + String destSuperName = null; + if (srcSuper != null && destSuper != null) { + String srcSuperName = srcSuper.getName(); + destSuperName = destSuper.getName(); + if (!srcSuperName.equals(destSuperName)) + if (srcSuperName.equals(CtClass.javaLangObject)) + patch = true; + else + map.putIfNone(srcSuperName, destSuperName); + } + + // a stack map table is copied from srcInfo. + methodInfo = new MethodInfo(cp, srcInfo.getName(), srcInfo, map); + if (isCons && patch) + methodInfo.setSuperclass(destSuperName); + } + catch (NotFoundException e) { + throw new CannotCompileException(e); + } + catch (BadBytecode e) { + throw new CannotCompileException(e); + } + } + + protected void extendToString(StringBuffer buffer) { + buffer.append(' '); + buffer.append(getName()); + buffer.append(' '); + buffer.append(methodInfo.getDescriptor()); + } + + /** + * Returns the method or constructor name followed by parameter types + * such as <code>javassist.CtBehavior.stBody(String)</code>. + * + * @since 3.5 + */ + public abstract String getLongName(); + + /** + * Returns the MethodInfo representing this method/constructor in the + * class file. + */ + public MethodInfo getMethodInfo() { + declaringClass.checkModify(); + return methodInfo; + } + + /** + * Returns the MethodInfo representing the method/constructor in the + * class file (read only). + * Normal applications do not need calling this method. Use + * <code>getMethodInfo()</code>. + * + * <p>The <code>MethodInfo</code> object obtained by this method + * is read only. Changes to this object might not be reflected + * on a class file generated by <code>toBytecode()</code>, + * <code>toClass()</code>, etc in <code>CtClass</code>. + * + * <p>This method is available even if the <code>CtClass</code> + * containing this method is frozen. However, if the class is + * frozen, the <code>MethodInfo</code> might be also pruned. + * + * @see #getMethodInfo() + * @see CtClass#isFrozen() + * @see CtClass#prune() + */ + public MethodInfo getMethodInfo2() { return methodInfo; } + + /** + * Obtains the modifiers of the method/constructor. + * + * @return modifiers encoded with + * <code>javassist.Modifier</code>. + * @see Modifier + */ + public int getModifiers() { + return AccessFlag.toModifier(methodInfo.getAccessFlags()); + } + + /** + * Sets the encoded modifiers of the method/constructor. + * + * <p>Changing the modifiers may cause a problem. + * For example, if a non-static method is changed to static, + * the method will be rejected by the bytecode verifier. + * + * @see Modifier + */ + public void setModifiers(int mod) { + declaringClass.checkModify(); + methodInfo.setAccessFlags(AccessFlag.of(mod)); + } + + /** + * Returns true if the class has the specified annotation class. + * + * @param clz the annotation class. + * @return <code>true</code> if the annotation is found, + * otherwise <code>false</code>. + * @since 3.11 + */ + public boolean hasAnnotation(Class clz) { + MethodInfo mi = getMethodInfo2(); + AnnotationsAttribute ainfo = (AnnotationsAttribute) + mi.getAttribute(AnnotationsAttribute.invisibleTag); + AnnotationsAttribute ainfo2 = (AnnotationsAttribute) + mi.getAttribute(AnnotationsAttribute.visibleTag); + return CtClassType.hasAnnotationType(clz, + getDeclaringClass().getClassPool(), + ainfo, ainfo2); + } + + /** + * Returns the annotation if the class has the specified annotation class. + * For example, if an annotation <code>@Author</code> is associated + * with this method/constructor, an <code>Author</code> object is returned. + * The member values can be obtained by calling methods on + * the <code>Author</code> object. + * + * @param clz the annotation class. + * @return the annotation if found, otherwise <code>null</code>. + * @since 3.11 + */ + public Object getAnnotation(Class clz) throws ClassNotFoundException { + MethodInfo mi = getMethodInfo2(); + AnnotationsAttribute ainfo = (AnnotationsAttribute) + mi.getAttribute(AnnotationsAttribute.invisibleTag); + AnnotationsAttribute ainfo2 = (AnnotationsAttribute) + mi.getAttribute(AnnotationsAttribute.visibleTag); + return CtClassType.getAnnotationType(clz, + getDeclaringClass().getClassPool(), + ainfo, ainfo2); + } + + /** + * Returns the annotations associated with this method or constructor. + * + * @return an array of annotation-type objects. + * @see #getAvailableAnnotations() + * @since 3.1 + */ + public Object[] getAnnotations() throws ClassNotFoundException { + return getAnnotations(false); + } + + /** + * Returns the annotations associated with this method or constructor. + * If any annotations are not on the classpath, they are not included + * in the returned array. + * + * @return an array of annotation-type objects. + * @see #getAnnotations() + * @since 3.3 + */ + public Object[] getAvailableAnnotations(){ + try{ + return getAnnotations(true); + } + catch (ClassNotFoundException e){ + throw new RuntimeException("Unexpected exception", e); + } + } + + private Object[] getAnnotations(boolean ignoreNotFound) + throws ClassNotFoundException + { + MethodInfo mi = getMethodInfo2(); + AnnotationsAttribute ainfo = (AnnotationsAttribute) + mi.getAttribute(AnnotationsAttribute.invisibleTag); + AnnotationsAttribute ainfo2 = (AnnotationsAttribute) + mi.getAttribute(AnnotationsAttribute.visibleTag); + return CtClassType.toAnnotationType(ignoreNotFound, + getDeclaringClass().getClassPool(), + ainfo, ainfo2); + } + + /** + * Returns the parameter annotations associated with this method or constructor. + * + * @return an array of annotation-type objects. The length of the returned array is + * equal to the number of the formal parameters. If each parameter has no + * annotation, the elements of the returned array are empty arrays. + * + * @see #getAvailableParameterAnnotations() + * @see #getAnnotations() + * @since 3.1 + */ + public Object[][] getParameterAnnotations() throws ClassNotFoundException { + return getParameterAnnotations(false); + } + + /** + * Returns the parameter annotations associated with this method or constructor. + * If any annotations are not on the classpath, they are not included in the + * returned array. + * + * @return an array of annotation-type objects. The length of the returned array is + * equal to the number of the formal parameters. If each parameter has no + * annotation, the elements of the returned array are empty arrays. + * + * @see #getParameterAnnotations() + * @see #getAvailableAnnotations() + * @since 3.3 + */ + public Object[][] getAvailableParameterAnnotations(){ + try { + return getParameterAnnotations(true); + } + catch(ClassNotFoundException e) { + throw new RuntimeException("Unexpected exception", e); + } + } + + Object[][] getParameterAnnotations(boolean ignoreNotFound) + throws ClassNotFoundException + { + MethodInfo mi = getMethodInfo2(); + ParameterAnnotationsAttribute ainfo = (ParameterAnnotationsAttribute) + mi.getAttribute(ParameterAnnotationsAttribute.invisibleTag); + ParameterAnnotationsAttribute ainfo2 = (ParameterAnnotationsAttribute) + mi.getAttribute(ParameterAnnotationsAttribute.visibleTag); + return CtClassType.toAnnotationType(ignoreNotFound, + getDeclaringClass().getClassPool(), + ainfo, ainfo2, mi); + } + + /** + * Obtains parameter types of this method/constructor. + */ + public CtClass[] getParameterTypes() throws NotFoundException { + return Descriptor.getParameterTypes(methodInfo.getDescriptor(), + declaringClass.getClassPool()); + } + + /** + * Obtains the type of the returned value. + */ + CtClass getReturnType0() throws NotFoundException { + return Descriptor.getReturnType(methodInfo.getDescriptor(), + declaringClass.getClassPool()); + } + + /** + * Returns the method signature (the parameter types + * and the return type). + * The method signature is represented by a character string + * called method descriptor, which is defined in the JVM specification. + * If two methods/constructors have + * the same parameter types + * and the return type, <code>getSignature()</code> returns the + * same string (the return type of constructors is <code>void</code>). + * + * <p>Note that the returned string is not the type signature + * contained in the <code>SignatureAttirbute</code>. It is + * a descriptor. To obtain a type signature, call the following + * methods: + * + * <ul><pre>getMethodInfo().getAttribute(SignatureAttribute.tag) + * </pre></ul> + * + * @see javassist.bytecode.Descriptor + * @see javassist.bytecode.SignatureAttribute + */ + public String getSignature() { + return methodInfo.getDescriptor(); + } + + /** + * Obtains exceptions that this method/constructor may throw. + * + * @return a zero-length array if there is no throws clause. + */ + public CtClass[] getExceptionTypes() throws NotFoundException { + String[] exceptions; + ExceptionsAttribute ea = methodInfo.getExceptionsAttribute(); + if (ea == null) + exceptions = null; + else + exceptions = ea.getExceptions(); + + return declaringClass.getClassPool().get(exceptions); + } + + /** + * Sets exceptions that this method/constructor may throw. + */ + public void setExceptionTypes(CtClass[] types) throws NotFoundException { + declaringClass.checkModify(); + if (types == null || types.length == 0) { + methodInfo.removeExceptionsAttribute(); + return; + } + + String[] names = new String[types.length]; + for (int i = 0; i < types.length; ++i) + names[i] = types[i].getName(); + + ExceptionsAttribute ea = methodInfo.getExceptionsAttribute(); + if (ea == null) { + ea = new ExceptionsAttribute(methodInfo.getConstPool()); + methodInfo.setExceptionsAttribute(ea); + } + + ea.setExceptions(names); + } + + /** + * Returns true if the body is empty. + */ + public abstract boolean isEmpty(); + + /** + * Sets a method/constructor body. + * + * @param src the source code representing the body. + * It must be a single statement or block. + * If it is <code>null</code>, the substituted + * body does nothing except returning zero or null. + */ + public void setBody(String src) throws CannotCompileException { + setBody(src, null, null); + } + + /** + * Sets a method/constructor body. + * + * @param src the source code representing the body. + * It must be a single statement or block. + * If it is <code>null</code>, the substituted + * body does nothing except returning zero or null. + * @param delegateObj the source text specifying the object + * that is called on by <code>$proceed()</code>. + * @param delegateMethod the name of the method + * that is called by <code>$proceed()</code>. + */ + public void setBody(String src, + String delegateObj, String delegateMethod) + throws CannotCompileException + { + CtClass cc = declaringClass; + cc.checkModify(); + try { + Javac jv = new Javac(cc); + if (delegateMethod != null) + jv.recordProceed(delegateObj, delegateMethod); + + Bytecode b = jv.compileBody(this, src); + methodInfo.setCodeAttribute(b.toCodeAttribute()); + methodInfo.setAccessFlags(methodInfo.getAccessFlags() + & ~AccessFlag.ABSTRACT); + methodInfo.rebuildStackMapIf6(cc.getClassPool(), cc.getClassFile2()); + declaringClass.rebuildClassFile(); + } + catch (CompileError e) { + throw new CannotCompileException(e); + } catch (BadBytecode e) { + throw new CannotCompileException(e); + } + } + + static void setBody0(CtClass srcClass, MethodInfo srcInfo, + CtClass destClass, MethodInfo destInfo, + ClassMap map) + throws CannotCompileException + { + destClass.checkModify(); + + map = new ClassMap(map); + map.put(srcClass.getName(), destClass.getName()); + try { + CodeAttribute cattr = srcInfo.getCodeAttribute(); + if (cattr != null) { + ConstPool cp = destInfo.getConstPool(); + CodeAttribute ca = (CodeAttribute)cattr.copy(cp, map); + destInfo.setCodeAttribute(ca); + // a stack map table is copied to destInfo. + } + } + catch (CodeAttribute.RuntimeCopyException e) { + /* the exception may be thrown by copy() in CodeAttribute. + */ + throw new CannotCompileException(e); + } + + destInfo.setAccessFlags(destInfo.getAccessFlags() + & ~AccessFlag.ABSTRACT); + destClass.rebuildClassFile(); + } + + /** + * Obtains an attribute with the given name. + * If that attribute is not found in the class file, this + * method returns null. + * + * <p>Note that an attribute is a data block specified by + * the class file format. It is not an annotation. + * See {@link javassist.bytecode.AttributeInfo}. + * + * @param name attribute name + */ + public byte[] getAttribute(String name) { + AttributeInfo ai = methodInfo.getAttribute(name); + if (ai == null) + return null; + else + return ai.get(); + } + + /** + * Adds an attribute. The attribute is saved in the class file. + * + * <p>Note that an attribute is a data block specified by + * the class file format. It is not an annotation. + * See {@link javassist.bytecode.AttributeInfo}. + * + * @param name attribute name + * @param data attribute value + */ + public void setAttribute(String name, byte[] data) { + declaringClass.checkModify(); + methodInfo.addAttribute(new AttributeInfo(methodInfo.getConstPool(), + name, data)); + } + + /** + * Declares to use <code>$cflow</code> for this method/constructor. + * If <code>$cflow</code> is used, the class files modified + * with Javassist requires a support class + * <code>javassist.runtime.Cflow</code> at runtime + * (other Javassist classes are not required at runtime). + * + * <p>Every <code>$cflow</code> variable is given a unique name. + * For example, if the given name is <code>"Point.paint"</code>, + * then the variable is indicated by <code>$cflow(Point.paint)</code>. + * + * @param name <code>$cflow</code> name. It can include + * alphabets, numbers, <code>_</code>, + * <code>$</code>, and <code>.</code> (dot). + * + * @see javassist.runtime.Cflow + */ + public void useCflow(String name) throws CannotCompileException { + CtClass cc = declaringClass; + cc.checkModify(); + ClassPool pool = cc.getClassPool(); + String fname; + int i = 0; + while (true) { + fname = "_cflow$" + i++; + try { + cc.getDeclaredField(fname); + } + catch(NotFoundException e) { + break; + } + } + + pool.recordCflow(name, declaringClass.getName(), fname); + try { + CtClass type = pool.get("javassist.runtime.Cflow"); + CtField field = new CtField(type, fname, cc); + field.setModifiers(Modifier.PUBLIC | Modifier.STATIC); + cc.addField(field, CtField.Initializer.byNew(type)); + insertBefore(fname + ".enter();", false); + String src = fname + ".exit();"; + insertAfter(src, true); + } + catch (NotFoundException e) { + throw new CannotCompileException(e); + } + } + + /** + * Declares a new local variable. The scope of this variable is the + * whole method body. The initial value of that variable is not set. + * The declared variable can be accessed in the code snippet inserted + * by <code>insertBefore()</code>, <code>insertAfter()</code>, etc. + * + * <p>If the second parameter <code>asFinally</code> to + * <code>insertAfter()</code> is true, the declared local variable + * is not visible from the code inserted by <code>insertAfter()</code>. + * + * @param name the name of the variable + * @param type the type of the variable + * @see #insertBefore(String) + * @see #insertAfter(String) + */ + public void addLocalVariable(String name, CtClass type) + throws CannotCompileException + { + declaringClass.checkModify(); + ConstPool cp = methodInfo.getConstPool(); + CodeAttribute ca = methodInfo.getCodeAttribute(); + if (ca == null) + throw new CannotCompileException("no method body"); + + LocalVariableAttribute va = (LocalVariableAttribute)ca.getAttribute( + LocalVariableAttribute.tag); + if (va == null) { + va = new LocalVariableAttribute(cp); + ca.getAttributes().add(va); + } + + int maxLocals = ca.getMaxLocals(); + String desc = Descriptor.of(type); + va.addEntry(0, ca.getCodeLength(), + cp.addUtf8Info(name), cp.addUtf8Info(desc), maxLocals); + ca.setMaxLocals(maxLocals + Descriptor.dataSize(desc)); + } + + /** + * Inserts a new parameter, which becomes the first parameter. + */ + public void insertParameter(CtClass type) + throws CannotCompileException + { + declaringClass.checkModify(); + String desc = methodInfo.getDescriptor(); + String desc2 = Descriptor.insertParameter(type, desc); + try { + addParameter2(Modifier.isStatic(getModifiers()) ? 0 : 1, type, desc); + } + catch (BadBytecode e) { + throw new CannotCompileException(e); + } + + methodInfo.setDescriptor(desc2); + } + + /** + * Appends a new parameter, which becomes the last parameter. + */ + public void addParameter(CtClass type) + throws CannotCompileException + { + declaringClass.checkModify(); + String desc = methodInfo.getDescriptor(); + String desc2 = Descriptor.appendParameter(type, desc); + int offset = Modifier.isStatic(getModifiers()) ? 0 : 1; + try { + addParameter2(offset + Descriptor.paramSize(desc), type, desc); + } + catch (BadBytecode e) { + throw new CannotCompileException(e); + } + + methodInfo.setDescriptor(desc2); + } + + private void addParameter2(int where, CtClass type, String desc) + throws BadBytecode + { + CodeAttribute ca = methodInfo.getCodeAttribute(); + if (ca != null) { + int size = 1; + char typeDesc = 'L'; + int classInfo = 0; + if (type.isPrimitive()) { + CtPrimitiveType cpt = (CtPrimitiveType)type; + size = cpt.getDataSize(); + typeDesc = cpt.getDescriptor(); + } + else + classInfo = methodInfo.getConstPool().addClassInfo(type); + + ca.insertLocalVar(where, size); + LocalVariableAttribute va + = (LocalVariableAttribute) + ca.getAttribute(LocalVariableAttribute.tag); + if (va != null) + va.shiftIndex(where, size); + + StackMapTable smt = (StackMapTable)ca.getAttribute(StackMapTable.tag); + if (smt != null) + smt.insertLocal(where, StackMapTable.typeTagOf(typeDesc), classInfo); + + StackMap sm = (StackMap)ca.getAttribute(StackMap.tag); + if (sm != null) + sm.insertLocal(where, StackMapTable.typeTagOf(typeDesc), classInfo); + } + } + + /** + * Modifies the method/constructor body. + * + * @param converter specifies how to modify. + */ + public void instrument(CodeConverter converter) + throws CannotCompileException + { + declaringClass.checkModify(); + ConstPool cp = methodInfo.getConstPool(); + converter.doit(getDeclaringClass(), methodInfo, cp); + } + + /** + * Modifies the method/constructor body. + * + * @param editor specifies how to modify. + */ + public void instrument(ExprEditor editor) + throws CannotCompileException + { + // if the class is not frozen, + // does not turn the modified flag on. + if (declaringClass.isFrozen()) + declaringClass.checkModify(); + + if (editor.doit(declaringClass, methodInfo)) + declaringClass.checkModify(); + } + + /** + * Inserts bytecode at the beginning of the body. + * + * <p>If this object represents a constructor, + * the bytecode is inserted before + * a constructor in the super class or this class is called. + * Therefore, the inserted bytecode is subject to constraints described + * in Section 4.8.2 of The Java Virtual Machine Specification (2nd ed). + * For example, it cannot access instance fields or methods although + * it may assign a value to an instance field directly declared in this + * class. Accessing static fields and methods is allowed. + * Use <code>insertBeforeBody()</code> in <code>CtConstructor</code>. + * + * @param src the source code representing the inserted bytecode. + * It must be a single statement or block. + * @see CtConstructor#insertBeforeBody(String) + */ + public void insertBefore(String src) throws CannotCompileException { + insertBefore(src, true); + } + + private void insertBefore(String src, boolean rebuild) + throws CannotCompileException + { + CtClass cc = declaringClass; + cc.checkModify(); + CodeAttribute ca = methodInfo.getCodeAttribute(); + if (ca == null) + throw new CannotCompileException("no method body"); + + CodeIterator iterator = ca.iterator(); + Javac jv = new Javac(cc); + try { + int nvars = jv.recordParams(getParameterTypes(), + Modifier.isStatic(getModifiers())); + jv.recordParamNames(ca, nvars); + jv.recordLocalVariables(ca, 0); + jv.recordType(getReturnType0()); + jv.compileStmnt(src); + Bytecode b = jv.getBytecode(); + int stack = b.getMaxStack(); + int locals = b.getMaxLocals(); + + if (stack > ca.getMaxStack()) + ca.setMaxStack(stack); + + if (locals > ca.getMaxLocals()) + ca.setMaxLocals(locals); + + int pos = iterator.insertEx(b.get()); + iterator.insert(b.getExceptionTable(), pos); + if (rebuild) + methodInfo.rebuildStackMapIf6(cc.getClassPool(), cc.getClassFile2()); + } + catch (NotFoundException e) { + throw new CannotCompileException(e); + } + catch (CompileError e) { + throw new CannotCompileException(e); + } + catch (BadBytecode e) { + throw new CannotCompileException(e); + } + } + + /** + * Inserts bytecode at the end of the body. + * The bytecode is inserted just before every return insturction. + * It is not executed when an exception is thrown. + * + * @param src the source code representing the inserted bytecode. + * It must be a single statement or block. + */ + public void insertAfter(String src) + throws CannotCompileException + { + insertAfter(src, false); + } + + /** + * Inserts bytecode at the end of the body. + * The bytecode is inserted just before every return insturction. + * + * @param src the source code representing the inserted bytecode. + * It must be a single statement or block. + * @param asFinally true if the inserted bytecode is executed + * not only when the control normally returns + * but also when an exception is thrown. + * If this parameter is true, the inserted code cannot + * access local variables. + */ + public void insertAfter(String src, boolean asFinally) + throws CannotCompileException + { + CtClass cc = declaringClass; + cc.checkModify(); + ConstPool pool = methodInfo.getConstPool(); + CodeAttribute ca = methodInfo.getCodeAttribute(); + if (ca == null) + throw new CannotCompileException("no method body"); + + CodeIterator iterator = ca.iterator(); + int retAddr = ca.getMaxLocals(); + Bytecode b = new Bytecode(pool, 0, retAddr + 1); + b.setStackDepth(ca.getMaxStack() + 1); + Javac jv = new Javac(b, cc); + try { + int nvars = jv.recordParams(getParameterTypes(), + Modifier.isStatic(getModifiers())); + jv.recordParamNames(ca, nvars); + CtClass rtype = getReturnType0(); + int varNo = jv.recordReturnType(rtype, true); + jv.recordLocalVariables(ca, 0); + + // finally clause for exceptions + int handlerLen = insertAfterHandler(asFinally, b, rtype, varNo, + jv, src); + // finally clause for normal termination + insertAfterAdvice(b, jv, src, pool, rtype, varNo); + + ca.setMaxStack(b.getMaxStack()); + ca.setMaxLocals(b.getMaxLocals()); + + int gapPos = iterator.append(b.get()); + iterator.append(b.getExceptionTable(), gapPos); + + if (asFinally) + ca.getExceptionTable().add(getStartPosOfBody(ca), gapPos, gapPos, 0); + + int gapLen = iterator.getCodeLength() - gapPos - handlerLen; + int subr = iterator.getCodeLength() - gapLen; + + while (iterator.hasNext()) { + int pos = iterator.next(); + if (pos >= subr) + break; + + int c = iterator.byteAt(pos); + if (c == Opcode.ARETURN || c == Opcode.IRETURN + || c == Opcode.FRETURN || c == Opcode.LRETURN + || c == Opcode.DRETURN || c == Opcode.RETURN) { + insertGoto(iterator, subr, pos); + subr = iterator.getCodeLength() - gapLen; + } + } + + methodInfo.rebuildStackMapIf6(cc.getClassPool(), cc.getClassFile2()); + } + catch (NotFoundException e) { + throw new CannotCompileException(e); + } + catch (CompileError e) { + throw new CannotCompileException(e); + } + catch (BadBytecode e) { + throw new CannotCompileException(e); + } + } + + private void insertAfterAdvice(Bytecode code, Javac jv, String src, + ConstPool cp, CtClass rtype, int varNo) + throws CompileError + { + if (rtype == CtClass.voidType) { + code.addOpcode(Opcode.ACONST_NULL); + code.addAstore(varNo); + jv.compileStmnt(src); + code.addOpcode(Opcode.RETURN); + if (code.getMaxLocals() < 1) + code.setMaxLocals(1); + } + else { + code.addStore(varNo, rtype); + jv.compileStmnt(src); + code.addLoad(varNo, rtype); + if (rtype.isPrimitive()) + code.addOpcode(((CtPrimitiveType)rtype).getReturnOp()); + else + code.addOpcode(Opcode.ARETURN); + } + } + + /* + * assert subr > pos + */ + private void insertGoto(CodeIterator iterator, int subr, int pos) + throws BadBytecode + { + iterator.setMark(subr); + // the gap length might be a multiple of 4. + iterator.writeByte(Opcode.NOP, pos); + boolean wide = subr + 2 - pos > Short.MAX_VALUE; + pos = iterator.insertGapAt(pos, wide ? 4 : 2, false).position; + int offset = iterator.getMark() - pos; + if (wide) { + iterator.writeByte(Opcode.GOTO_W, pos); + iterator.write32bit(offset, pos + 1); + } + else if (offset <= Short.MAX_VALUE) { + iterator.writeByte(Opcode.GOTO, pos); + iterator.write16bit(offset, pos + 1); + } + else { + pos = iterator.insertGapAt(pos, 2, false).position; + iterator.writeByte(Opcode.GOTO_W, pos); + iterator.write32bit(iterator.getMark() - pos, pos + 1); + } + } + + /* insert a finally clause + */ + private int insertAfterHandler(boolean asFinally, Bytecode b, + CtClass rtype, int returnVarNo, + Javac javac, String src) + throws CompileError + { + if (!asFinally) + return 0; + + int var = b.getMaxLocals(); + b.incMaxLocals(1); + int pc = b.currentPc(); + b.addAstore(var); // store an exception + if (rtype.isPrimitive()) { + char c = ((CtPrimitiveType)rtype).getDescriptor(); + if (c == 'D') { + b.addDconst(0.0); + b.addDstore(returnVarNo); + } + else if (c == 'F') { + b.addFconst(0); + b.addFstore(returnVarNo); + } + else if (c == 'J') { + b.addLconst(0); + b.addLstore(returnVarNo); + } + else if (c == 'V') { + b.addOpcode(Opcode.ACONST_NULL); + b.addAstore(returnVarNo); + } + else { // int, boolean, char, short, ... + b.addIconst(0); + b.addIstore(returnVarNo); + } + } + else { + b.addOpcode(Opcode.ACONST_NULL); + b.addAstore(returnVarNo); + } + + javac.compileStmnt(src); + b.addAload(var); + b.addOpcode(Opcode.ATHROW); + return b.currentPc() - pc; + } + + /* -- OLD version -- + + public void insertAfter(String src) throws CannotCompileException { + declaringClass.checkModify(); + CodeAttribute ca = methodInfo.getCodeAttribute(); + CodeIterator iterator = ca.iterator(); + Bytecode b = new Bytecode(methodInfo.getConstPool(), + ca.getMaxStack(), ca.getMaxLocals()); + b.setStackDepth(ca.getMaxStack()); + Javac jv = new Javac(b, declaringClass); + try { + jv.recordParams(getParameterTypes(), + Modifier.isStatic(getModifiers())); + CtClass rtype = getReturnType0(); + int varNo = jv.recordReturnType(rtype, true); + boolean isVoid = rtype == CtClass.voidType; + if (isVoid) { + b.addOpcode(Opcode.ACONST_NULL); + b.addAstore(varNo); + jv.compileStmnt(src); + } + else { + b.addStore(varNo, rtype); + jv.compileStmnt(src); + b.addLoad(varNo, rtype); + } + + byte[] code = b.get(); + ca.setMaxStack(b.getMaxStack()); + ca.setMaxLocals(b.getMaxLocals()); + while (iterator.hasNext()) { + int pos = iterator.next(); + int c = iterator.byteAt(pos); + if (c == Opcode.ARETURN || c == Opcode.IRETURN + || c == Opcode.FRETURN || c == Opcode.LRETURN + || c == Opcode.DRETURN || c == Opcode.RETURN) + iterator.insert(pos, code); + } + } + catch (NotFoundException e) { + throw new CannotCompileException(e); + } + catch (CompileError e) { + throw new CannotCompileException(e); + } + catch (BadBytecode e) { + throw new CannotCompileException(e); + } + } + */ + + /** + * Adds a catch clause that handles an exception thrown in the + * body. The catch clause must end with a return or throw statement. + * + * @param src the source code representing the catch clause. + * It must be a single statement or block. + * @param exceptionType the type of the exception handled by the + * catch clause. + */ + public void addCatch(String src, CtClass exceptionType) + throws CannotCompileException + { + addCatch(src, exceptionType, "$e"); + } + + /** + * Adds a catch clause that handles an exception thrown in the + * body. The catch clause must end with a return or throw statement. + * + * @param src the source code representing the catch clause. + * It must be a single statement or block. + * @param exceptionType the type of the exception handled by the + * catch clause. + * @param exceptionName the name of the variable containing the + * caught exception, for example, + * <code>$e</code>. + */ + public void addCatch(String src, CtClass exceptionType, + String exceptionName) + throws CannotCompileException + { + CtClass cc = declaringClass; + cc.checkModify(); + ConstPool cp = methodInfo.getConstPool(); + CodeAttribute ca = methodInfo.getCodeAttribute(); + CodeIterator iterator = ca.iterator(); + Bytecode b = new Bytecode(cp, ca.getMaxStack(), ca.getMaxLocals()); + b.setStackDepth(1); + Javac jv = new Javac(b, cc); + try { + jv.recordParams(getParameterTypes(), + Modifier.isStatic(getModifiers())); + int var = jv.recordVariable(exceptionType, exceptionName); + b.addAstore(var); + jv.compileStmnt(src); + + int stack = b.getMaxStack(); + int locals = b.getMaxLocals(); + + if (stack > ca.getMaxStack()) + ca.setMaxStack(stack); + + if (locals > ca.getMaxLocals()) + ca.setMaxLocals(locals); + + int len = iterator.getCodeLength(); + int pos = iterator.append(b.get()); + ca.getExceptionTable().add(getStartPosOfBody(ca), len, len, + cp.addClassInfo(exceptionType)); + iterator.append(b.getExceptionTable(), pos); + methodInfo.rebuildStackMapIf6(cc.getClassPool(), cc.getClassFile2()); + } + catch (NotFoundException e) { + throw new CannotCompileException(e); + } + catch (CompileError e) { + throw new CannotCompileException(e); + } catch (BadBytecode e) { + throw new CannotCompileException(e); + } + } + + /* CtConstructor overrides this method. + */ + int getStartPosOfBody(CodeAttribute ca) throws CannotCompileException { + return 0; + } + + /** + * Inserts bytecode at the specified line in the body. + * It is equivalent to: + * + * <br><code>insertAt(lineNum, true, src)</code> + * + * <br>See this method as well. + * + * @param lineNum the line number. The bytecode is inserted at the + * beginning of the code at the line specified by this + * line number. + * @param src the source code representing the inserted bytecode. + * It must be a single statement or block. + * @return the line number at which the bytecode has been inserted. + * + * @see CtBehavior#insertAt(int,boolean,String) + */ + public int insertAt(int lineNum, String src) + throws CannotCompileException + { + return insertAt(lineNum, true, src); + } + + /** + * Inserts bytecode at the specified line in the body. + * + * <p>If there is not + * a statement at the specified line, the bytecode might be inserted + * at the line including the first statement after that line specified. + * For example, if there is only a closing brace at that line, the + * bytecode would be inserted at another line below. + * To know exactly where the bytecode will be inserted, call with + * <code>modify</code> set to <code>false</code>. + * + * @param lineNum the line number. The bytecode is inserted at the + * beginning of the code at the line specified by this + * line number. + * @param modify if false, this method does not insert the bytecode. + * It instead only returns the line number at which + * the bytecode would be inserted. + * @param src the source code representing the inserted bytecode. + * It must be a single statement or block. + * If modify is false, the value of src can be null. + * @return the line number at which the bytecode has been inserted. + */ + public int insertAt(int lineNum, boolean modify, String src) + throws CannotCompileException + { + CodeAttribute ca = methodInfo.getCodeAttribute(); + if (ca == null) + throw new CannotCompileException("no method body"); + + LineNumberAttribute ainfo + = (LineNumberAttribute)ca.getAttribute(LineNumberAttribute.tag); + if (ainfo == null) + throw new CannotCompileException("no line number info"); + + LineNumberAttribute.Pc pc = ainfo.toNearPc(lineNum); + lineNum = pc.line; + int index = pc.index; + if (!modify) + return lineNum; + + CtClass cc = declaringClass; + cc.checkModify(); + CodeIterator iterator = ca.iterator(); + Javac jv = new Javac(cc); + try { + jv.recordLocalVariables(ca, index); + jv.recordParams(getParameterTypes(), + Modifier.isStatic(getModifiers())); + jv.setMaxLocals(ca.getMaxLocals()); + jv.compileStmnt(src); + Bytecode b = jv.getBytecode(); + int locals = b.getMaxLocals(); + int stack = b.getMaxStack(); + ca.setMaxLocals(locals); + + /* We assume that there is no values in the operand stack + * at the position where the bytecode is inserted. + */ + if (stack > ca.getMaxStack()) + ca.setMaxStack(stack); + + index = iterator.insertAt(index, b.get()); + iterator.insert(b.getExceptionTable(), index); + methodInfo.rebuildStackMapIf6(cc.getClassPool(), cc.getClassFile2()); + return lineNum; + } + catch (NotFoundException e) { + throw new CannotCompileException(e); + } + catch (CompileError e) { + throw new CannotCompileException(e); + } + catch (BadBytecode e) { + throw new CannotCompileException(e); + } + } +} diff --git a/src/main/javassist/CtClass.java b/src/main/javassist/CtClass.java new file mode 100644 index 0000000..e805c20 --- /dev/null +++ b/src/main/javassist/CtClass.java @@ -0,0 +1,1440 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist; + +import java.io.BufferedOutputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.net.URL; +import java.security.ProtectionDomain; +import java.util.Collection; +import javassist.bytecode.ClassFile; +import javassist.bytecode.Descriptor; +import javassist.bytecode.Opcode; +import javassist.expr.ExprEditor; + +/* Note: + * + * This class is an abstract class and several methods just return null + * or throw an exception. Those methods are overridden in subclasses + * of this class. Read the source code of CtClassType if you are + * interested in the implementation of Javassist. + * + * Subclasses of CtClass are CtClassType, CtPrimitiveType, and CtArray. + */ + +/** + * An instance of <code>CtClass</code> represents a class. + * It is obtained from <code>ClassPool</code>. + * + * @see ClassPool#get(String) + */ +public abstract class CtClass { + protected String qualifiedName; + + /** + * The version number of this release. + */ + public static final String version = "3.14.0.GA"; + + /** + * Prints the version number and the copyright notice. + * + * <p>The following command invokes this method: + * + * <ul><pre>java -jar javassist.jar</pre></ul> + */ + public static void main(String[] args) { + System.out.println("Javassist version " + CtClass.version); + System.out.println("Copyright (C) 1999-2010 Shigeru Chiba." + + " All Rights Reserved."); + } + + static final String javaLangObject = "java.lang.Object"; + + /** + * The <code>CtClass</code> object representing + * the <code>boolean</code> type. + */ + public static CtClass booleanType; + + /** + * The <code>CtClass</code> object representing + * the <code>char</code> type. + */ + public static CtClass charType; + + /** + * The <code>CtClass</code> object representing + * the <code>byte</code> type. + */ + public static CtClass byteType; + + /** + * The <code>CtClass</code> object representing + * the <code>short</code> type. + */ + public static CtClass shortType; + + /** + * The <code>CtClass</code> object representing + * the <code>int</code> type. + */ + public static CtClass intType; + + /** + * The <code>CtClass</code> object representing + * the <code>long</code> type. + */ + public static CtClass longType; + + /** + * The <code>CtClass</code> object representing + * the <code>float</code> type. + */ + public static CtClass floatType; + + /** + * The <code>CtClass</code> object representing + * the <code>double</code> type. + */ + public static CtClass doubleType; + + /** + * The <code>CtClass</code> object representing + * the <code>void</code> type. + */ + public static CtClass voidType; + + static CtClass[] primitiveTypes; + + static { + primitiveTypes = new CtClass[9]; + + booleanType = + new CtPrimitiveType("boolean", 'Z', "java.lang.Boolean", + "booleanValue", "()Z", Opcode.IRETURN, + Opcode.T_BOOLEAN, 1); + primitiveTypes[0] = booleanType; + + charType = new CtPrimitiveType("char", 'C', "java.lang.Character", + "charValue", "()C", Opcode.IRETURN, + Opcode.T_CHAR, 1); + primitiveTypes[1] = charType; + + byteType = new CtPrimitiveType("byte", 'B', "java.lang.Byte", + "byteValue", "()B", Opcode.IRETURN, + Opcode.T_BYTE, 1); + primitiveTypes[2] = byteType; + + shortType = new CtPrimitiveType("short", 'S', "java.lang.Short", + "shortValue", "()S", Opcode.IRETURN, + Opcode.T_SHORT, 1); + primitiveTypes[3] = shortType; + + intType = new CtPrimitiveType("int", 'I', "java.lang.Integer", + "intValue", "()I", Opcode.IRETURN, + Opcode.T_INT, 1); + primitiveTypes[4] = intType; + + longType = new CtPrimitiveType("long", 'J', "java.lang.Long", + "longValue", "()J", Opcode.LRETURN, + Opcode.T_LONG, 2); + primitiveTypes[5] = longType; + + floatType = new CtPrimitiveType("float", 'F', "java.lang.Float", + "floatValue", "()F", Opcode.FRETURN, + Opcode.T_FLOAT, 1); + primitiveTypes[6] = floatType; + + doubleType = new CtPrimitiveType("double", 'D', "java.lang.Double", + "doubleValue", "()D", Opcode.DRETURN, + Opcode.T_DOUBLE, 2); + primitiveTypes[7] = doubleType; + + voidType = new CtPrimitiveType("void", 'V', "java.lang.Void", + null, null, Opcode.RETURN, 0, 0); + primitiveTypes[8] = voidType; + } + + protected CtClass(String name) { + qualifiedName = name; + } + + /** + * Converts the object to a string. + */ + public String toString() { + StringBuffer buf = new StringBuffer(getClass().getName()); + buf.append("@"); + buf.append(Integer.toHexString(hashCode())); + buf.append("["); + extendToString(buf); + buf.append("]"); + return buf.toString(); + } + + /** + * Implemented in subclasses to add to the {@link #toString()} result. + * Subclasses should put a space before each token added to the buffer. + */ + protected void extendToString(StringBuffer buffer) { + buffer.append(getName()); + } + + /** + * Returns a <code>ClassPool</code> for this class. + */ + public ClassPool getClassPool() { return null; } + + /** + * Returns a class file for this class. + * + * <p>This method is not available if <code>isFrozen()</code> + * is true. + */ + public ClassFile getClassFile() { + checkModify(); + return getClassFile2(); + } + + /** + * Returns a class file for this class (read only). + * Normal applications do not need calling this method. Use + * <code>getClassFile()</code>. + * + * <p>The <code>ClassFile</code> object obtained by this method + * is read only. Changes to this object might not be reflected + * on a class file generated by <code>toBytecode()</code>, + * <code>toClass()</code>, etc. + * + * <p>This method is available even if <code>isFrozen()</code> + * is true. However, if the class is frozen, it might be also + * pruned. + * + * @see CtClass#getClassFile() + * @see CtClass#isFrozen() + * @see CtClass#prune() + */ + public ClassFile getClassFile2() { return null; } + + /** + * Undocumented method. Do not use; internal-use only. + */ + public javassist.compiler.AccessorMaker getAccessorMaker() { + return null; + } + + /** + * Returns the uniform resource locator (URL) of the class file. + */ + public URL getURL() throws NotFoundException { + throw new NotFoundException(getName()); + } + + /** + * Returns true if the definition of the class has been modified. + */ + public boolean isModified() { return false; } + + /** + * Returns true if the class has been loaded or written out + * and thus it cannot be modified any more. + * + * @see #defrost() + * @see #detach() + */ + public boolean isFrozen() { return true; } + + /** + * Makes the class frozen. + * + * @see #isFrozen() + * @see #defrost() + * @since 3.6 + */ + public void freeze() {} + + /* Note: this method is overridden by CtClassType + */ + void checkModify() throws RuntimeException { + if (isFrozen()) + throw new RuntimeException(getName() + " class is frozen"); + + // isModified() must return true after this method is invoked. + } + + /** + * Defrosts the class so that the class can be modified again. + * + * <p>To avoid changes that will be never reflected, + * the class is frozen to be unmodifiable if it is loaded or + * written out. This method should be called only in a case + * that the class will be reloaded or written out later again. + * + * <p>If <code>defrost()</code> will be called later, pruning + * must be disallowed in advance. + * + * @see #isFrozen() + * @see #stopPruning(boolean) + * @see #detach() + */ + public void defrost() { + throw new RuntimeException("cannot defrost " + getName()); + } + + /** + * Returns <code>true</code> if this object represents a primitive + * Java type: boolean, byte, char, short, int, long, float, double, + * or void. + */ + public boolean isPrimitive() { return false; } + + /** + * Returns <code>true</code> if this object represents an array type. + */ + public boolean isArray() { + return false; + } + + /** + * If this object represents an array, this method returns the component + * type of the array. Otherwise, it returns <code>null</code>. + */ + public CtClass getComponentType() throws NotFoundException { + return null; + } + + /** + * Returns <code>true</code> if this class extends or implements + * <code>clazz</code>. It also returns <code>true</code> if + * this class is the same as <code>clazz</code>. + */ + public boolean subtypeOf(CtClass clazz) throws NotFoundException { + return this == clazz || getName().equals(clazz.getName()); + } + + /** + * Obtains the fully-qualified name of the class. + */ + public String getName() { return qualifiedName; } + + /** + * Obtains the not-qualified class name. + */ + public final String getSimpleName() { + String qname = qualifiedName; + int index = qname.lastIndexOf('.'); + if (index < 0) + return qname; + else + return qname.substring(index + 1); + } + + /** + * Obtains the package name. It may be <code>null</code>. + */ + public final String getPackageName() { + String qname = qualifiedName; + int index = qname.lastIndexOf('.'); + if (index < 0) + return null; + else + return qname.substring(0, index); + } + + /** + * Sets the class name + * + * @param name fully-qualified name + */ + public void setName(String name) { + checkModify(); + if (name != null) + qualifiedName = name; + } + + /** + * Substitutes <code>newName</code> for all occurrences of a class + * name <code>oldName</code> in the class file. + * + * @param oldName replaced class name + * @param newName substituted class name + */ + public void replaceClassName(String oldName, String newName) { + checkModify(); + } + + /** + * Changes class names appearing in the class file according to the + * given <code>map</code>. + * + * <p>All the class names appearing in the class file are tested + * with <code>map</code> to determine whether each class name is + * replaced or not. Thus this method can be used for collecting + * all the class names in the class file. To do that, first define + * a subclass of <code>ClassMap</code> so that <code>get()</code> + * records all the given parameters. Then, make an instance of + * that subclass as an empty hash-table. Finally, pass that instance + * to this method. After this method finishes, that instance would + * contain all the class names appearing in the class file. + * + * @param map the hashtable associating replaced class names + * with substituted names. + */ + public void replaceClassName(ClassMap map) { + checkModify(); + } + + /** + * Returns a collection of the names of all the classes + * referenced in this class. + * That collection includes the name of this class. + * + * <p>This method may return <code>null</code>. + * + * @return a <code>Collection<String></code> object. + */ + public synchronized Collection getRefClasses() { + ClassFile cf = getClassFile2(); + if (cf != null) { + ClassMap cm = new ClassMap() { + public void put(String oldname, String newname) { + put0(oldname, newname); + } + + public Object get(Object jvmClassName) { + String n = toJavaName((String)jvmClassName); + put0(n, n); + return null; + } + + public void fix(String name) {} + }; + cf.getRefClasses(cm); + return cm.values(); + } + else + return null; + } + + /** + * Determines whether this object represents a class or an interface. + * It returns <code>true</code> if this object represents an interface. + */ + public boolean isInterface() { + return false; + } + + /** + * Determines whether this object represents an annotation type. + * It returns <code>true</code> if this object represents an annotation type. + * + * @since 3.2 + */ + public boolean isAnnotation() { + return false; + } + + /** + * Determines whether this object represents an enum. + * It returns <code>true</code> if this object represents an enum. + * + * @since 3.2 + */ + public boolean isEnum() { + return false; + } + + /** + * Returns the modifiers for this class, encoded in an integer. + * For decoding, use <code>javassist.Modifier</code>. + * + * <p>If the class is a static nested class (a.k.a. static inner class), + * the returned modifiers include <code>Modifier.STATIC</code>. + * + * @see Modifier + */ + public int getModifiers() { + return 0; + } + + /** + * Returns true if the class has the specified annotation class. + * + * @param clz the annotation class. + * @return <code>true</code> if the annotation is found, otherwise <code>false</code>. + * @since 3.11 + */ + public boolean hasAnnotation(Class clz) { + return false; + } + + /** + * Returns the annotation if the class has the specified annotation class. + * For example, if an annotation <code>@Author</code> is associated + * with this class, an <code>Author</code> object is returned. + * The member values can be obtained by calling methods on + * the <code>Author</code> object. + * + * @param clz the annotation class. + * @return the annotation if found, otherwise <code>null</code>. + * @since 3.11 + */ + public Object getAnnotation(Class clz) throws ClassNotFoundException { + return null; + } + + /** + * Returns the annotations associated with this class. + * For example, if an annotation <code>@Author</code> is associated + * with this class, the returned array contains an <code>Author</code> + * object. The member values can be obtained by calling methods on + * the <code>Author</code> object. + * + * @return an array of annotation-type objects. + * @see CtMember#getAnnotations() + * @since 3.1 + */ + public Object[] getAnnotations() throws ClassNotFoundException { + return new Object[0]; + } + + /** + * Returns the annotations associated with this class. + * This method is equivalent to <code>getAnnotations()</code> + * except that, if any annotations are not on the classpath, + * they are not included in the returned array. + * + * @return an array of annotation-type objects. + * @see #getAnnotations() + * @see CtMember#getAvailableAnnotations() + * @since 3.3 + */ + public Object[] getAvailableAnnotations(){ + return new Object[0]; + } + + /** + * Returns an array of nested classes declared in the class. + * Nested classes are inner classes, anonymous classes, local classes, + * and static nested classes. + * + * @since 3.2 + */ + public CtClass[] getNestedClasses() throws NotFoundException { + return new CtClass[0]; + } + + /** + * Sets the modifiers. + * + * <p>If the class is a nested class, this method also modifies + * the class declaring that nested class (i.e. the enclosing + * class is modified). + * + * @param mod modifiers encoded by + * <code>javassist.Modifier</code> + * @see Modifier + */ + public void setModifiers(int mod) { + checkModify(); + } + + /** + * Determines whether the class directly or indirectly extends + * the given class. If this class extends a class A and + * the class A extends a class B, then subclassof(B) returns true. + * + * <p>This method returns true if the given class is identical to + * the class represented by this object. + */ + public boolean subclassOf(CtClass superclass) { + return false; + } + + /** + * Obtains the class object representing the superclass of the + * class. + * It returns null if this object represents the + * <code>java.lang.Object</code> class and thus it does not have + * the super class. + * + * <p>If this object represents an interface, this method + * always returns the <code>java.lang.Object</code> class. + * To obtain the super interfaces + * extended by that interface, call <code>getInterfaces()</code>. + */ + public CtClass getSuperclass() throws NotFoundException { + return null; + } + + /** + * Changes a super class unless this object represents an interface. + * The new super class must be compatible with the old one; for example, + * it should inherit from the old super class. + * + * <p>If this object represents an interface, this method is equivalent + * to <code>addInterface()</code>; it appends <code>clazz</code> to + * the list of the super interfaces extended by that interface. + * Note that an interface can extend multiple super interfaces. + * + * @see #replaceClassName(String, String) + * @see #replaceClassName(ClassMap) + */ + public void setSuperclass(CtClass clazz) throws CannotCompileException { + checkModify(); + } + + /** + * Obtains the class objects representing the interfaces implemented + * by the class or, if this object represents an interface, the interfaces + * extended by that interface. + */ + public CtClass[] getInterfaces() throws NotFoundException { + return new CtClass[0]; + } + + /** + * Sets implemented interfaces. If this object represents an interface, + * this method sets the interfaces extended by that interface. + * + * @param list a list of the <code>CtClass</code> objects + * representing interfaces, or + * <code>null</code> if the class implements + * no interfaces. + */ + public void setInterfaces(CtClass[] list) { + checkModify(); + } + + /** + * Adds an interface. + * + * @param anInterface the added interface. + */ + public void addInterface(CtClass anInterface) { + checkModify(); + } + + /** + * If this class is a member class or interface of another class, + * then the class enclosing this class is returned. + * + * @return null if this class is a top-level class or an anonymous class. + */ + public CtClass getDeclaringClass() throws NotFoundException { + return null; + } + + /** + * Returns the immediately enclosing method of this class. + * This method works only with JDK 1.5 or later. + * + * @return null if this class is not a local class or an anonymous + * class. + */ + public CtMethod getEnclosingMethod() throws NotFoundException { + return null; + } + + /** + * Makes a new public nested class. If this method is called, + * the <code>CtClass</code>, which encloses the nested class, is modified + * since a class file includes a list of nested classes. + * + * <p>The current implementation only supports a static nested class. + * <code>isStatic</code> must be true. + * + * @param name the simple name of the nested class. + * @param isStatic true if the nested class is static. + */ + public CtClass makeNestedClass(String name, boolean isStatic) { + throw new RuntimeException(getName() + " is not a class"); + } + + /** + * Returns an array containing <code>CtField</code> objects + * representing all the non-private fields of the class. + * That array includes non-private fields inherited from the + * superclasses. + */ + public CtField[] getFields() { return new CtField[0]; } + + /** + * Returns the field with the specified name. The returned field + * may be a private field declared in a super class or interface. + */ + public CtField getField(String name) throws NotFoundException { + return getField(name, null); + } + + /** + * Returns the field with the specified name and type. The returned field + * may be a private field declared in a super class or interface. + * Unlike Java, the JVM allows a class to have + * multiple fields with the same name but different types. + * + * @param name the field name. + * @param desc the type descriptor of the field. It is available by + * {@link CtField#getSignature()}. + * @see CtField#getSignature() + */ + public CtField getField(String name, String desc) throws NotFoundException { + throw new NotFoundException(name); + } + + /** + * @return null if the specified field is not found. + */ + CtField getField2(String name, String desc) { return null; } + + /** + * Gets all the fields declared in the class. The inherited fields + * are not included. + * + * <p>Note: the result does not include inherited fields. + */ + public CtField[] getDeclaredFields() { return new CtField[0]; } + + /** + * Retrieves the field with the specified name among the fields + * declared in the class. + * + * <p>Note: this method does not search the super classes. + */ + public CtField getDeclaredField(String name) throws NotFoundException { + throw new NotFoundException(name); + } + + /** + * Retrieves the field with the specified name and type among the fields + * declared in the class. Unlike Java, the JVM allows a class to have + * multiple fields with the same name but different types. + * + * <p>Note: this method does not search the super classes. + * + * @param name the field name. + * @param desc the type descriptor of the field. It is available by + * {@link CtField#getSignature()}. + * @see CtField#getSignature() + */ + public CtField getDeclaredField(String name, String desc) throws NotFoundException { + throw new NotFoundException(name); + } + + /** + * Gets all the constructors and methods declared in the class. + */ + public CtBehavior[] getDeclaredBehaviors() { + return new CtBehavior[0]; + } + + /** + * Returns an array containing <code>CtConstructor</code> objects + * representing all the non-private constructors of the class. + */ + public CtConstructor[] getConstructors() { + return new CtConstructor[0]; + } + + /** + * Returns the constructor with the given signature, + * which is represented by a character string + * called method descriptor. + * For details of the method descriptor, see the JVM specification + * or <code>javassist.bytecode.Descriptor</code>. + * + * @param desc method descriptor + * @see javassist.bytecode.Descriptor + */ + public CtConstructor getConstructor(String desc) + throws NotFoundException + { + throw new NotFoundException("no such constructor"); + } + + /** + * Gets all the constructors declared in the class. + * + * @see javassist.CtConstructor + */ + public CtConstructor[] getDeclaredConstructors() { + return new CtConstructor[0]; + } + + /** + * Returns a constructor receiving the specified parameters. + * + * @param params parameter types. + */ + public CtConstructor getDeclaredConstructor(CtClass[] params) + throws NotFoundException + { + String desc = Descriptor.ofConstructor(params); + return getConstructor(desc); + } + + /** + * Gets the class initializer (static constructor) + * declared in the class. + * This method returns <code>null</code> if + * no class initializer is not declared. + * + * @see #makeClassInitializer() + * @see javassist.CtConstructor + */ + public CtConstructor getClassInitializer() { + return null; + } + + /** + * Returns an array containing <code>CtMethod</code> objects + * representing all the non-private methods of the class. + * That array includes non-private methods inherited from the + * superclasses. + */ + public CtMethod[] getMethods() { + return new CtMethod[0]; + } + + /** + * Returns the method with the given name and signature. + * The returned method may be declared in a super class. + * The method signature is represented by a character string + * called method descriptor, + * which is defined in the JVM specification. + * + * @param name method name + * @param desc method descriptor + * @see CtBehavior#getSignature() + * @see javassist.bytecode.Descriptor + */ + public CtMethod getMethod(String name, String desc) + throws NotFoundException + { + throw new NotFoundException(name); + } + + /** + * Gets all methods declared in the class. The inherited methods + * are not included. + * + * @see javassist.CtMethod + */ + public CtMethod[] getDeclaredMethods() { + return new CtMethod[0]; + } + + /** + * Retrieves the method with the specified name and parameter types + * among the methods declared in the class. + * + * <p>Note: this method does not search the superclasses. + * + * @param name method name + * @param params parameter types + * @see javassist.CtMethod + */ + public CtMethod getDeclaredMethod(String name, CtClass[] params) + throws NotFoundException + { + throw new NotFoundException(name); + } + + /** + * Retrieves the method with the specified name among the methods + * declared in the class. If there are multiple methods with + * the specified name, then this method returns one of them. + * + * <p>Note: this method does not search the superclasses. + * + * @see javassist.CtMethod + */ + public CtMethod getDeclaredMethod(String name) throws NotFoundException { + throw new NotFoundException(name); + } + + /** + * Makes an empty class initializer (static constructor). + * If the class already includes a class initializer, + * this method returns it. + * + * @see #getClassInitializer() + */ + public CtConstructor makeClassInitializer() + throws CannotCompileException + { + throw new CannotCompileException("not a class"); + } + + /** + * Adds a constructor. To add a class initializer (static constructor), + * call <code>makeClassInitializer()</code>. + * + * @see #makeClassInitializer() + */ + public void addConstructor(CtConstructor c) + throws CannotCompileException + { + checkModify(); + } + + /** + * Removes a constructor declared in this class. + * + * @param c removed constructor. + * @throws NotFoundException if the constructor is not found. + */ + public void removeConstructor(CtConstructor c) throws NotFoundException { + checkModify(); + } + + /** + * Adds a method. + */ + public void addMethod(CtMethod m) throws CannotCompileException { + checkModify(); + } + + /** + * Removes a method declared in this class. + * + * @param m removed method. + * @throws NotFoundException if the method is not found. + */ + public void removeMethod(CtMethod m) throws NotFoundException { + checkModify(); + } + + /** + * Adds a field. + * + * <p>The <code>CtField</code> belonging to another + * <code>CtClass</code> cannot be directly added to this class. + * Only a field created for this class can be added. + * + * @see javassist.CtField#CtField(CtField,CtClass) + */ + public void addField(CtField f) throws CannotCompileException { + addField(f, (CtField.Initializer)null); + } + + /** + * Adds a field with an initial value. + * + * <p>The <code>CtField</code> belonging to another + * <code>CtClass</code> cannot be directly added to this class. + * Only a field created for this class can be added. + * + * <p>The initial value is given as an expression written in Java. + * Any regular Java expression can be used for specifying the initial + * value. The followings are examples. + * + * <ul><pre> + * cc.addField(f, "0") // the initial value is 0. + * cc.addField(f, "i + 1") // i + 1. + * cc.addField(f, "new Point()"); // a Point object. + * </pre></ul> + * + * <p>Here, the type of variable <code>cc</code> is <code>CtClass</code>. + * The type of <code>f</code> is <code>CtField</code>. + * + * <p>Note: do not change the modifier of the field + * (in particular, do not add or remove <code>static</code> + * to/from the modifier) + * after it is added to the class by <code>addField()</code>. + * + * @param init an expression for the initial value. + * + * @see javassist.CtField.Initializer#byExpr(String) + * @see javassist.CtField#CtField(CtField,CtClass) + */ + public void addField(CtField f, String init) + throws CannotCompileException + { + checkModify(); + } + + /** + * Adds a field with an initial value. + * + * <p>The <code>CtField</code> belonging to another + * <code>CtClass</code> cannot be directly added to this class. + * Only a field created for this class can be added. + * + * <p>For example, + * + * <ul><pre> + * CtClass cc = ...; + * addField(new CtField(CtClass.intType, "i", cc), + * CtField.Initializer.constant(1)); + * </pre></ul> + * + * <p>This code adds an <code>int</code> field named "i". The + * initial value of this field is 1. + * + * @param init specifies the initial value of the field. + * + * @see javassist.CtField#CtField(CtField,CtClass) + */ + public void addField(CtField f, CtField.Initializer init) + throws CannotCompileException + { + checkModify(); + } + + /** + * Removes a field declared in this class. + * + * @param f removed field. + * @throws NotFoundException if the field is not found. + */ + public void removeField(CtField f) throws NotFoundException { + checkModify(); + } + + /** + * Obtains an attribute with the given name. + * If that attribute is not found in the class file, this + * method returns null. + * + * <p>This is a convenient method mainly for obtaining + * a user-defined attribute. For dealing with attributes, see the + * <code>javassist.bytecode</code> package. For example, the following + * expression returns all the attributes of a class file. + * + * <ul><pre> + * getClassFile().getAttributes() + * </pre></ul> + * + * @param name attribute name + * @see javassist.bytecode.AttributeInfo + */ + public byte[] getAttribute(String name) { + return null; + } + + /** + * Adds a named attribute. + * An arbitrary data (smaller than 64Kb) can be saved in the class + * file. Some attribute name are reserved by the JVM. + * The attributes with the non-reserved names are ignored when a + * class file is loaded into the JVM. + * If there is already an attribute with + * the same name, this method substitutes the new one for it. + * + * <p>This is a convenient method mainly for adding + * a user-defined attribute. For dealing with attributes, see the + * <code>javassist.bytecode</code> package. For example, the following + * expression adds an attribute <code>info</code> to a class file. + * + * <ul><pre> + * getClassFile().addAttribute(info) + * </pre></ul> + * + * @param name attribute name + * @param data attribute value + * @see javassist.bytecode.AttributeInfo + */ + public void setAttribute(String name, byte[] data) { + checkModify(); + } + + /** + * Applies the given converter to all methods and constructors + * declared in the class. This method calls <code>instrument()</code> + * on every <code>CtMethod</code> and <code>CtConstructor</code> object + * in the class. + * + * @param converter specifies how to modify. + */ + public void instrument(CodeConverter converter) + throws CannotCompileException + { + checkModify(); + } + + /** + * Modifies the bodies of all methods and constructors + * declared in the class. This method calls <code>instrument()</code> + * on every <code>CtMethod</code> and <code>CtConstructor</code> object + * in the class. + * + * @param editor specifies how to modify. + */ + public void instrument(ExprEditor editor) + throws CannotCompileException + { + checkModify(); + } + + /** + * Converts this class to a <code>java.lang.Class</code> object. + * Once this method is called, further modifications are not + * allowed any more. + * To load the class, this method uses the context class loader + * of the current thread. If the program is running on some application + * server, the context class loader might be inappropriate to load the + * class. + * + * <p>This method is provided for convenience. If you need more + * complex functionality, you should write your own class loader. + * + * <p>Note: this method calls <code>toClass()</code> + * in <code>ClassPool</code>. + * + * <p><b>Warining:</b> A Class object returned by this method may not + * work with a security manager or a signed jar file because a + * protection domain is not specified. + * + * @see #toClass(java.lang.ClassLoader,ProtectionDomain) + * @see ClassPool#toClass(CtClass) + */ + public Class toClass() throws CannotCompileException { + return getClassPool().toClass(this); + } + + /** + * Converts this class to a <code>java.lang.Class</code> object. + * Once this method is called, further modifications are not allowed + * any more. + * + * <p>The class file represented by this <code>CtClass</code> is + * loaded by the given class loader to construct a + * <code>java.lang.Class</code> object. Since a private method + * on the class loader is invoked through the reflection API, + * the caller must have permissions to do that. + * + * <p>An easy way to obtain <code>ProtectionDomain</code> object is + * to call <code>getProtectionDomain()</code> + * in <code>java.lang.Class</code>. It returns the domain that + * the class belongs to. + * + * <p>This method is provided for convenience. If you need more + * complex functionality, you should write your own class loader. + * + * <p>Note: this method calls <code>toClass()</code> + * in <code>ClassPool</code>. + * + * @param loader the class loader used to load this class. + * If it is null, the class loader returned by + * {@link ClassPool#getClassLoader()} is used. + * @param domain the protection domain that the class belongs to. + * If it is null, the default domain created + * by <code>java.lang.ClassLoader</code> is used. + * @see ClassPool#toClass(CtClass,java.lang.ClassLoader) + * @since 3.3 + */ + public Class toClass(ClassLoader loader, ProtectionDomain domain) + throws CannotCompileException + { + ClassPool cp = getClassPool(); + if (loader == null) + loader = cp.getClassLoader(); + + return cp.toClass(this, loader, domain); + } + + /** + * Converts this class to a <code>java.lang.Class</code> object. + * + * <p><b>Warining:</b> A Class object returned by this method may not + * work with a security manager or a signed jar file because a + * protection domain is not specified. + * + * @deprecated Replaced by {@link #toClass(ClassLoader,ProtectionDomain)} + */ + public final Class toClass(ClassLoader loader) + throws CannotCompileException + { + return getClassPool().toClass(this, loader); + } + + /** + * Removes this <code>CtClass</code> object from the + * <code>ClassPool</code>. + * After this method is called, any method cannot be called on the + * removed <code>CtClass</code> object. + * + * <p>If <code>get()</code> in <code>ClassPool</code> is called + * with the name of the removed method, + * the <code>ClassPool</code> will read the class file again + * and constructs another <code>CtClass</code> object representing + * the same class. + */ + public void detach() { + ClassPool cp = getClassPool(); + CtClass obj = cp.removeCached(getName()); + if (obj != this) + cp.cacheCtClass(getName(), obj, false); + } + + /** + * Disallows (or allows) automatically pruning this <code>CtClass</code> + * object. + * + * <p> + * Javassist can automatically prune a <code>CtClass</code> object + * when <code>toBytecode()</code> (or <code>toClass()</code>, + * <code>writeFile()</code>) is called. + * Since a <code>ClassPool</code> holds all instances of <code>CtClass</code> + * even after <code>toBytecode()</code> (or <code>toClass()</code>, + * <code>writeFile()</code>) is called, pruning may significantly + * save memory consumption. + * + * <p>If <code>ClassPool.doPruning</code> is true, the automatic pruning + * is on by default. Otherwise, it is off. The default value of + * <code>ClassPool.doPruning</code> is false. + * + * @param stop disallow pruning if true. Otherwise, allow. + * @return the previous status of pruning. true if pruning is already stopped. + * + * @see #detach() + * @see #prune() + * @see ClassPool#doPruning + */ + public boolean stopPruning(boolean stop) { return true; } + + /** + * Discards unnecessary attributes, in particular, + * <code>CodeAttribute</code>s (method bodies) of the class, + * to minimize the memory footprint. + * After calling this method, the class is read only. + * It cannot be modified any more. + * Furthermore, <code>toBytecode()</code>, + * <code>writeFile()</code>, <code>toClass()</code>, + * or <code>instrument()</code> cannot be called. + * However, the method names and signatures in the class etc. + * are still accessible. + * + * <p><code>toBytecode()</code>, <code>writeFile()</code>, and + * <code>toClass()</code> internally call this method if + * automatic pruning is on. + * + * <p>According to some experiments, pruning does not really reduce + * memory consumption. Only about 20%. Since pruning takes time, + * it might not pay off. So the automatic pruning is off by default. + * + * @see #stopPruning(boolean) + * @see #detach() + * @see ClassPool#doPruning + * + * @see #toBytecode() + * @see #toClass() + * @see #writeFile() + * @see #instrument(CodeConverter) + * @see #instrument(ExprEditor) + */ + public void prune() {} + + /* Called by get() in ClassPool. + * CtClassType overrides this method. + */ + void incGetCounter() {} + + /** + * If this method is called, the class file will be + * rebuilt when it is finally generated by + * <code>toBytecode()</code> and <code>writeFile()</code>. + * For a performance reason, the symbol table of the + * class file may contain unused entries, for example, + * after a method or a filed is deleted. + * This method + * removes those unused entries. This removal will + * minimize the size of the class file. + * + * @since 3.8.1 + */ + public void rebuildClassFile() {} + + /** + * Converts this class to a class file. + * Once this method is called, further modifications are not + * possible any more. + * + * @return the contents of the class file. + */ + public byte[] toBytecode() throws IOException, CannotCompileException { + ByteArrayOutputStream barray = new ByteArrayOutputStream(); + DataOutputStream out = new DataOutputStream(barray); + try { + toBytecode(out); + } + finally { + out.close(); + } + + return barray.toByteArray(); + } + + /** + * Writes a class file represented by this <code>CtClass</code> + * object in the current directory. + * Once this method is called, further modifications are not + * possible any more. + * + * @see #debugWriteFile() + */ + public void writeFile() + throws NotFoundException, IOException, CannotCompileException + { + writeFile("."); + } + + /** + * Writes a class file represented by this <code>CtClass</code> + * object on a local disk. + * Once this method is called, further modifications are not + * possible any more. + * + * @param directoryName it must end without a directory separator. + * @see #debugWriteFile(String) + */ + public void writeFile(String directoryName) + throws CannotCompileException, IOException + { + String classname = getName(); + String filename = directoryName + File.separatorChar + + classname.replace('.', File.separatorChar) + ".class"; + int pos = filename.lastIndexOf(File.separatorChar); + if (pos > 0) { + String dir = filename.substring(0, pos); + if (!dir.equals(".")) + new File(dir).mkdirs(); + } + + DataOutputStream out + = new DataOutputStream(new BufferedOutputStream( + new DelayedFileOutputStream(filename))); + try { + toBytecode(out); + } + finally { + out.close(); + } + } + + /** + * Writes a class file as <code>writeFile()</code> does although this + * method does not prune or freeze the class after writing the class + * file. Note that, once <code>writeFile()</code> or <code>toBytecode()</code> + * is called, it cannot be called again since the class is pruned and frozen. + * This method would be useful for debugging. + */ + public void debugWriteFile() { + debugWriteFile("."); + } + + /** + * Writes a class file as <code>writeFile()</code> does although this + * method does not prune or freeze the class after writing the class + * file. Note that, once <code>writeFile()</code> or <code>toBytecode()</code> + * is called, it cannot be called again since the class is pruned and frozen. + * This method would be useful for debugging. + * + * @param directoryName it must end without a directory separator. + */ + public void debugWriteFile(String directoryName) { + try { + boolean p = stopPruning(true); + writeFile(directoryName); + defrost(); + stopPruning(p); + } + catch (Exception e) { + throw new RuntimeException(e); + } + } + + static class DelayedFileOutputStream extends OutputStream { + private FileOutputStream file; + private String filename; + + DelayedFileOutputStream(String name) { + file = null; + filename = name; + } + + private void init() throws IOException { + if (file == null) + file = new FileOutputStream(filename); + } + + public void write(int b) throws IOException { + init(); + file.write(b); + } + + public void write(byte[] b) throws IOException { + init(); + file.write(b); + } + + public void write(byte[] b, int off, int len) throws IOException { + init(); + file.write(b, off, len); + + } + + public void flush() throws IOException { + init(); + file.flush(); + } + + public void close() throws IOException { + init(); + file.close(); + } + } + + /** + * Converts this class to a class file. + * Once this method is called, further modifications are not + * possible any more. + * + * <p>This method dose not close the output stream in the end. + * + * @param out the output stream that a class file is written to. + */ + public void toBytecode(DataOutputStream out) + throws CannotCompileException, IOException + { + throw new CannotCompileException("not a class"); + } + + /** + * Makes a unique member name. This method guarantees that + * the returned name is not used as a prefix of any methods + * or fields visible in this class. + * If the returned name is XYZ, then any method or field names + * in this class do not start with XYZ. + * + * @param prefix the prefix of the member name. + */ + public String makeUniqueName(String prefix) { + throw new RuntimeException("not available in " + getName()); + } + + /* Invoked from ClassPool#compress(). + * This method is overridden by CtClassType. + */ + void compress() {} +} diff --git a/src/main/javassist/CtClassType.java b/src/main/javassist/CtClassType.java new file mode 100644 index 0000000..dad5db7 --- /dev/null +++ b/src/main/javassist/CtClassType.java @@ -0,0 +1,1695 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist; + +import java.lang.ref.WeakReference; +import java.io.BufferedInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.List; +import java.util.Set; + +import javassist.bytecode.AccessFlag; +import javassist.bytecode.AttributeInfo; +import javassist.bytecode.AnnotationsAttribute; +import javassist.bytecode.BadBytecode; +import javassist.bytecode.Bytecode; +import javassist.bytecode.ClassFile; +import javassist.bytecode.CodeAttribute; +import javassist.bytecode.ConstantAttribute; +import javassist.bytecode.CodeIterator; +import javassist.bytecode.ConstPool; +import javassist.bytecode.Descriptor; +import javassist.bytecode.EnclosingMethodAttribute; +import javassist.bytecode.FieldInfo; +import javassist.bytecode.InnerClassesAttribute; +import javassist.bytecode.MethodInfo; +import javassist.bytecode.ParameterAnnotationsAttribute; +import javassist.bytecode.annotation.Annotation; +import javassist.compiler.AccessorMaker; +import javassist.compiler.CompileError; +import javassist.compiler.Javac; +import javassist.expr.ExprEditor; + +/** + * Class types. + */ +class CtClassType extends CtClass { + ClassPool classPool; + boolean wasChanged; + private boolean wasFrozen; + boolean wasPruned; + boolean gcConstPool; // if true, the constant pool entries will be garbage collected. + ClassFile classfile; + byte[] rawClassfile; // backup storage + + private WeakReference memberCache; + private AccessorMaker accessors; + + private FieldInitLink fieldInitializers; + private Hashtable hiddenMethods; // must be synchronous + private int uniqueNumberSeed; + + private boolean doPruning = ClassPool.doPruning; + private int getCount; + private static final int GET_THRESHOLD = 2; // see compress() + + CtClassType(String name, ClassPool cp) { + super(name); + classPool = cp; + wasChanged = wasFrozen = wasPruned = gcConstPool = false; + classfile = null; + rawClassfile = null; + memberCache = null; + accessors = null; + fieldInitializers = null; + hiddenMethods = null; + uniqueNumberSeed = 0; + getCount = 0; + } + + CtClassType(InputStream ins, ClassPool cp) throws IOException { + this((String)null, cp); + classfile = new ClassFile(new DataInputStream(ins)); + qualifiedName = classfile.getName(); + } + + protected void extendToString(StringBuffer buffer) { + if (wasChanged) + buffer.append("changed "); + + if (wasFrozen) + buffer.append("frozen "); + + if (wasPruned) + buffer.append("pruned "); + + buffer.append(Modifier.toString(getModifiers())); + buffer.append(" class "); + buffer.append(getName()); + + try { + CtClass ext = getSuperclass(); + if (ext != null) { + String name = ext.getName(); + if (!name.equals("java.lang.Object")) + buffer.append(" extends " + ext.getName()); + } + } + catch (NotFoundException e) { + buffer.append(" extends ??"); + } + + try { + CtClass[] intf = getInterfaces(); + if (intf.length > 0) + buffer.append(" implements "); + + for (int i = 0; i < intf.length; ++i) { + buffer.append(intf[i].getName()); + buffer.append(", "); + } + } + catch (NotFoundException e) { + buffer.append(" extends ??"); + } + + CtMember.Cache memCache = getMembers(); + exToString(buffer, " fields=", + memCache.fieldHead(), memCache.lastField()); + exToString(buffer, " constructors=", + memCache.consHead(), memCache.lastCons()); + exToString(buffer, " methods=", + memCache.methodHead(), memCache.lastMethod()); + } + + private void exToString(StringBuffer buffer, String msg, + CtMember head, CtMember tail) { + buffer.append(msg); + while (head != tail) { + head = head.next(); + buffer.append(head); + buffer.append(", "); + } + } + + public AccessorMaker getAccessorMaker() { + if (accessors == null) + accessors = new AccessorMaker(this); + + return accessors; + } + + public ClassFile getClassFile2() { + ClassFile cfile = classfile; + if (cfile != null) + return cfile; + + classPool.compress(); + if (rawClassfile != null) { + try { + classfile = new ClassFile(new DataInputStream( + new ByteArrayInputStream(rawClassfile))); + rawClassfile = null; + getCount = GET_THRESHOLD; + return classfile; + } + catch (IOException e) { + throw new RuntimeException(e.toString(), e); + } + } + + InputStream fin = null; + try { + fin = classPool.openClassfile(getName()); + if (fin == null) + throw new NotFoundException(getName()); + + fin = new BufferedInputStream(fin); + ClassFile cf = new ClassFile(new DataInputStream(fin)); + if (!cf.getName().equals(qualifiedName)) + throw new RuntimeException("cannot find " + qualifiedName + ": " + + cf.getName() + " found in " + + qualifiedName.replace('.', '/') + ".class"); + + classfile = cf; + return cf; + } + catch (NotFoundException e) { + throw new RuntimeException(e.toString(), e); + } + catch (IOException e) { + throw new RuntimeException(e.toString(), e); + } + finally { + if (fin != null) + try { + fin.close(); + } + catch (IOException e) {} + } + } + + /* Inherited from CtClass. Called by get() in ClassPool. + * + * @see javassist.CtClass#incGetCounter() + * @see #toBytecode(DataOutputStream) + */ + final void incGetCounter() { ++getCount; } + + /** + * Invoked from ClassPool#compress(). + * It releases the class files that have not been recently used + * if they are unmodified. + */ + void compress() { + if (getCount < GET_THRESHOLD) + if (!isModified() && ClassPool.releaseUnmodifiedClassFile) + removeClassFile(); + else if (isFrozen() && !wasPruned) + saveClassFile(); + + getCount = 0; + } + + /** + * Converts a ClassFile object into a byte array + * for saving memory space. + */ + private synchronized void saveClassFile() { + /* getMembers() and releaseClassFile() are also synchronized. + */ + if (classfile == null || hasMemberCache() != null) + return; + + ByteArrayOutputStream barray = new ByteArrayOutputStream(); + DataOutputStream out = new DataOutputStream(barray); + try { + classfile.write(out); + barray.close(); + rawClassfile = barray.toByteArray(); + classfile = null; + } + catch (IOException e) {} + } + + private synchronized void removeClassFile() { + if (classfile != null && !isModified() && hasMemberCache() == null) + classfile = null; + } + + public ClassPool getClassPool() { return classPool; } + + void setClassPool(ClassPool cp) { classPool = cp; } + + public URL getURL() throws NotFoundException { + URL url = classPool.find(getName()); + if (url == null) + throw new NotFoundException(getName()); + else + return url; + } + + public boolean isModified() { return wasChanged; } + + public boolean isFrozen() { return wasFrozen; } + + public void freeze() { wasFrozen = true; } + + void checkModify() throws RuntimeException { + if (isFrozen()) { + String msg = getName() + " class is frozen"; + if (wasPruned) + msg += " and pruned"; + + throw new RuntimeException(msg); + } + + wasChanged = true; + } + + public void defrost() { + checkPruned("defrost"); + wasFrozen = false; + } + + public boolean subtypeOf(CtClass clazz) throws NotFoundException { + int i; + String cname = clazz.getName(); + if (this == clazz || getName().equals(cname)) + return true; + + ClassFile file = getClassFile2(); + String supername = file.getSuperclass(); + if (supername != null && supername.equals(cname)) + return true; + + String[] ifs = file.getInterfaces(); + int num = ifs.length; + for (i = 0; i < num; ++i) + if (ifs[i].equals(cname)) + return true; + + if (supername != null && classPool.get(supername).subtypeOf(clazz)) + return true; + + for (i = 0; i < num; ++i) + if (classPool.get(ifs[i]).subtypeOf(clazz)) + return true; + + return false; + } + + public void setName(String name) throws RuntimeException { + String oldname = getName(); + if (name.equals(oldname)) + return; + + // check this in advance although classNameChanged() below does. + classPool.checkNotFrozen(name); + ClassFile cf = getClassFile2(); + super.setName(name); + cf.setName(name); + nameReplaced(); + classPool.classNameChanged(oldname, this); + } + + public void replaceClassName(ClassMap classnames) + throws RuntimeException + { + String oldClassName = getName(); + String newClassName + = (String)classnames.get(Descriptor.toJvmName(oldClassName)); + if (newClassName != null) { + newClassName = Descriptor.toJavaName(newClassName); + // check this in advance although classNameChanged() below does. + classPool.checkNotFrozen(newClassName); + } + + super.replaceClassName(classnames); + ClassFile cf = getClassFile2(); + cf.renameClass(classnames); + nameReplaced(); + + if (newClassName != null) { + super.setName(newClassName); + classPool.classNameChanged(oldClassName, this); + } + } + + public void replaceClassName(String oldname, String newname) + throws RuntimeException + { + String thisname = getName(); + if (thisname.equals(oldname)) + setName(newname); + else { + super.replaceClassName(oldname, newname); + getClassFile2().renameClass(oldname, newname); + nameReplaced(); + } + } + + public boolean isInterface() { + return Modifier.isInterface(getModifiers()); + } + + public boolean isAnnotation() { + return Modifier.isAnnotation(getModifiers()); + } + + public boolean isEnum() { + return Modifier.isEnum(getModifiers()); + } + + public int getModifiers() { + ClassFile cf = getClassFile2(); + int acc = cf.getAccessFlags(); + acc = AccessFlag.clear(acc, AccessFlag.SUPER); + int inner = cf.getInnerAccessFlags(); + if (inner != -1 && (inner & AccessFlag.STATIC) != 0) + acc |= AccessFlag.STATIC; + + return AccessFlag.toModifier(acc); + } + + public CtClass[] getNestedClasses() throws NotFoundException { + ClassFile cf = getClassFile2(); + InnerClassesAttribute ica + = (InnerClassesAttribute)cf.getAttribute(InnerClassesAttribute.tag); + if (ica == null) + return new CtClass[0]; + + String thisName = cf.getName() + "$"; + int n = ica.tableLength(); + ArrayList list = new ArrayList(n); + for (int i = 0; i < n; i++) { + String name = ica.innerClass(i); + if (name != null) + if (name.startsWith(thisName)) { + // if it is an immediate nested class + if (name.lastIndexOf('$') < thisName.length()) + list.add(classPool.get(name)); + } + } + + return (CtClass[])list.toArray(new CtClass[list.size()]); + } + + public void setModifiers(int mod) { + ClassFile cf = getClassFile2(); + if (Modifier.isStatic(mod)) { + int flags = cf.getInnerAccessFlags(); + if (flags != -1 && (flags & AccessFlag.STATIC) != 0) + mod = mod & ~Modifier.STATIC; + else + throw new RuntimeException("cannot change " + getName() + " into a static class"); + } + + checkModify(); + cf.setAccessFlags(AccessFlag.of(mod)); + } + + public boolean hasAnnotation(Class clz) { + ClassFile cf = getClassFile2(); + AnnotationsAttribute ainfo = (AnnotationsAttribute) + cf.getAttribute(AnnotationsAttribute.invisibleTag); + AnnotationsAttribute ainfo2 = (AnnotationsAttribute) + cf.getAttribute(AnnotationsAttribute.visibleTag); + return hasAnnotationType(clz, getClassPool(), ainfo, ainfo2); + } + + static boolean hasAnnotationType(Class clz, ClassPool cp, + AnnotationsAttribute a1, AnnotationsAttribute a2) + { + Annotation[] anno1, anno2; + + if (a1 == null) + anno1 = null; + else + anno1 = a1.getAnnotations(); + + if (a2 == null) + anno2 = null; + else + anno2 = a2.getAnnotations(); + + String typeName = clz.getName(); + if (anno1 != null) + for (int i = 0; i < anno1.length; i++) + if (anno1[i].getTypeName().equals(typeName)) + return true; + + if (anno2 != null) + for (int i = 0; i < anno2.length; i++) + if (anno2[i].getTypeName().equals(typeName)) + return true; + + return false; + } + + public Object getAnnotation(Class clz) throws ClassNotFoundException { + ClassFile cf = getClassFile2(); + AnnotationsAttribute ainfo = (AnnotationsAttribute) + cf.getAttribute(AnnotationsAttribute.invisibleTag); + AnnotationsAttribute ainfo2 = (AnnotationsAttribute) + cf.getAttribute(AnnotationsAttribute.visibleTag); + return getAnnotationType(clz, getClassPool(), ainfo, ainfo2); + } + + static Object getAnnotationType(Class clz, ClassPool cp, + AnnotationsAttribute a1, AnnotationsAttribute a2) + throws ClassNotFoundException + { + Annotation[] anno1, anno2; + + if (a1 == null) + anno1 = null; + else + anno1 = a1.getAnnotations(); + + if (a2 == null) + anno2 = null; + else + anno2 = a2.getAnnotations(); + + String typeName = clz.getName(); + if (anno1 != null) + for (int i = 0; i < anno1.length; i++) + if (anno1[i].getTypeName().equals(typeName)) + return toAnnoType(anno1[i], cp); + + if (anno2 != null) + for (int i = 0; i < anno2.length; i++) + if (anno2[i].getTypeName().equals(typeName)) + return toAnnoType(anno2[i], cp); + + return null; + } + + public Object[] getAnnotations() throws ClassNotFoundException { + return getAnnotations(false); + } + + public Object[] getAvailableAnnotations(){ + try { + return getAnnotations(true); + } + catch (ClassNotFoundException e) { + throw new RuntimeException("Unexpected exception ", e); + } + } + + private Object[] getAnnotations(boolean ignoreNotFound) + throws ClassNotFoundException + { + ClassFile cf = getClassFile2(); + AnnotationsAttribute ainfo = (AnnotationsAttribute) + cf.getAttribute(AnnotationsAttribute.invisibleTag); + AnnotationsAttribute ainfo2 = (AnnotationsAttribute) + cf.getAttribute(AnnotationsAttribute.visibleTag); + return toAnnotationType(ignoreNotFound, getClassPool(), ainfo, ainfo2); + } + + static Object[] toAnnotationType(boolean ignoreNotFound, ClassPool cp, + AnnotationsAttribute a1, AnnotationsAttribute a2) + throws ClassNotFoundException + { + Annotation[] anno1, anno2; + int size1, size2; + + if (a1 == null) { + anno1 = null; + size1 = 0; + } + else { + anno1 = a1.getAnnotations(); + size1 = anno1.length; + } + + if (a2 == null) { + anno2 = null; + size2 = 0; + } + else { + anno2 = a2.getAnnotations(); + size2 = anno2.length; + } + + if (!ignoreNotFound){ + Object[] result = new Object[size1 + size2]; + for (int i = 0; i < size1; i++) + result[i] = toAnnoType(anno1[i], cp); + + for (int j = 0; j < size2; j++) + result[j + size1] = toAnnoType(anno2[j], cp); + + return result; + } + else{ + ArrayList annotations = new ArrayList(); + for (int i = 0 ; i < size1 ; i++){ + try{ + annotations.add(toAnnoType(anno1[i], cp)); + } + catch(ClassNotFoundException e){} + } + for (int j = 0; j < size2; j++) { + try{ + annotations.add(toAnnoType(anno2[j], cp)); + } + catch(ClassNotFoundException e){} + } + + return annotations.toArray(); + } + } + + static Object[][] toAnnotationType(boolean ignoreNotFound, ClassPool cp, + ParameterAnnotationsAttribute a1, + ParameterAnnotationsAttribute a2, + MethodInfo minfo) + throws ClassNotFoundException + { + int numParameters = 0; + if (a1 != null) + numParameters = a1.numParameters(); + else if (a2 != null) + numParameters = a2.numParameters(); + else + numParameters = Descriptor.numOfParameters(minfo.getDescriptor()); + + Object[][] result = new Object[numParameters][]; + for (int i = 0; i < numParameters; i++) { + Annotation[] anno1, anno2; + int size1, size2; + + if (a1 == null) { + anno1 = null; + size1 = 0; + } + else { + anno1 = a1.getAnnotations()[i]; + size1 = anno1.length; + } + + if (a2 == null) { + anno2 = null; + size2 = 0; + } + else { + anno2 = a2.getAnnotations()[i]; + size2 = anno2.length; + } + + if (!ignoreNotFound){ + result[i] = new Object[size1 + size2]; + for (int j = 0; j < size1; ++j) + result[i][j] = toAnnoType(anno1[j], cp); + + for (int j = 0; j < size2; ++j) + result[i][j + size1] = toAnnoType(anno2[j], cp); + } + else{ + ArrayList annotations = new ArrayList(); + for (int j = 0 ; j < size1 ; j++){ + try{ + annotations.add(toAnnoType(anno1[j], cp)); + } + catch(ClassNotFoundException e){} + } + for (int j = 0; j < size2; j++){ + try{ + annotations.add(toAnnoType(anno2[j], cp)); + } + catch(ClassNotFoundException e){} + } + + result[i] = annotations.toArray(); + } + } + + return result; + } + + private static Object toAnnoType(Annotation anno, ClassPool cp) + throws ClassNotFoundException + { + try { + ClassLoader cl = cp.getClassLoader(); + return anno.toAnnotationType(cl, cp); + } + catch (ClassNotFoundException e) { + ClassLoader cl2 = cp.getClass().getClassLoader(); + return anno.toAnnotationType(cl2, cp); + } + } + + public boolean subclassOf(CtClass superclass) { + if (superclass == null) + return false; + + String superName = superclass.getName(); + CtClass curr = this; + try { + while (curr != null) { + if (curr.getName().equals(superName)) + return true; + + curr = curr.getSuperclass(); + } + } + catch (Exception ignored) {} + return false; + } + + public CtClass getSuperclass() throws NotFoundException { + String supername = getClassFile2().getSuperclass(); + if (supername == null) + return null; + else + return classPool.get(supername); + } + + public void setSuperclass(CtClass clazz) throws CannotCompileException { + checkModify(); + if (isInterface()) + addInterface(clazz); + else + getClassFile2().setSuperclass(clazz.getName()); + } + + public CtClass[] getInterfaces() throws NotFoundException { + String[] ifs = getClassFile2().getInterfaces(); + int num = ifs.length; + CtClass[] ifc = new CtClass[num]; + for (int i = 0; i < num; ++i) + ifc[i] = classPool.get(ifs[i]); + + return ifc; + } + + public void setInterfaces(CtClass[] list) { + checkModify(); + String[] ifs; + if (list == null) + ifs = new String[0]; + else { + int num = list.length; + ifs = new String[num]; + for (int i = 0; i < num; ++i) + ifs[i] = list[i].getName(); + } + + getClassFile2().setInterfaces(ifs); + } + + public void addInterface(CtClass anInterface) { + checkModify(); + if (anInterface != null) + getClassFile2().addInterface(anInterface.getName()); + } + + public CtClass getDeclaringClass() throws NotFoundException { + ClassFile cf = getClassFile2(); + InnerClassesAttribute ica = (InnerClassesAttribute)cf.getAttribute( + InnerClassesAttribute.tag); + if (ica == null) + return null; + + String name = getName(); + int n = ica.tableLength(); + for (int i = 0; i < n; ++i) + if (name.equals(ica.innerClass(i))) { + String outName = ica.outerClass(i); + if (outName != null) + return classPool.get(outName); + else { + // maybe anonymous or local class. + EnclosingMethodAttribute ema + = (EnclosingMethodAttribute)cf.getAttribute( + EnclosingMethodAttribute.tag); + if (ema != null) + return classPool.get(ema.className()); + } + } + + return null; + } + + public CtMethod getEnclosingMethod() throws NotFoundException { + ClassFile cf = getClassFile2(); + EnclosingMethodAttribute ema + = (EnclosingMethodAttribute)cf.getAttribute( + EnclosingMethodAttribute.tag); + if (ema != null) { + CtClass enc = classPool.get(ema.className()); + return enc.getMethod(ema.methodName(), ema.methodDescriptor()); + } + + return null; + } + + public CtClass makeNestedClass(String name, boolean isStatic) { + if (!isStatic) + throw new RuntimeException( + "sorry, only nested static class is supported"); + + checkModify(); + CtClass c = classPool.makeNestedClass(getName() + "$" + name); + ClassFile cf = getClassFile2(); + ClassFile cf2 = c.getClassFile2(); + InnerClassesAttribute ica = (InnerClassesAttribute)cf.getAttribute( + InnerClassesAttribute.tag); + if (ica == null) { + ica = new InnerClassesAttribute(cf.getConstPool()); + cf.addAttribute(ica); + } + + ica.append(c.getName(), this.getName(), name, + (cf2.getAccessFlags() & ~AccessFlag.SUPER) | AccessFlag.STATIC); + cf2.addAttribute(ica.copy(cf2.getConstPool(), null)); + return c; + } + + /* flush cached names. + */ + private void nameReplaced() { + CtMember.Cache cache = hasMemberCache(); + if (cache != null) { + CtMember mth = cache.methodHead(); + CtMember tail = cache.lastMethod(); + while (mth != tail) { + mth = mth.next(); + mth.nameReplaced(); + } + } + } + + /** + * Returns null if members are not cached. + */ + protected CtMember.Cache hasMemberCache() { + if (memberCache != null) + return (CtMember.Cache)memberCache.get(); + else + return null; + } + + protected synchronized CtMember.Cache getMembers() { + CtMember.Cache cache = null; + if (memberCache == null + || (cache = (CtMember.Cache)memberCache.get()) == null) { + cache = new CtMember.Cache(this); + makeFieldCache(cache); + makeBehaviorCache(cache); + memberCache = new WeakReference(cache); + } + + return cache; + } + + private void makeFieldCache(CtMember.Cache cache) { + List list = getClassFile2().getFields(); + int n = list.size(); + for (int i = 0; i < n; ++i) { + FieldInfo finfo = (FieldInfo)list.get(i); + CtField newField = new CtField(finfo, this); + cache.addField(newField); + } + } + + private void makeBehaviorCache(CtMember.Cache cache) { + List list = getClassFile2().getMethods(); + int n = list.size(); + for (int i = 0; i < n; ++i) { + MethodInfo minfo = (MethodInfo)list.get(i); + if (minfo.isMethod()) { + CtMethod newMethod = new CtMethod(minfo, this); + cache.addMethod(newMethod); + } + else { + CtConstructor newCons = new CtConstructor(minfo, this); + cache.addConstructor(newCons); + } + } + } + + public CtField[] getFields() { + ArrayList alist = new ArrayList(); + getFields(alist, this); + return (CtField[])alist.toArray(new CtField[alist.size()]); + } + + private static void getFields(ArrayList alist, CtClass cc) { + int i, num; + if (cc == null) + return; + + try { + getFields(alist, cc.getSuperclass()); + } + catch (NotFoundException e) {} + + try { + CtClass[] ifs = cc.getInterfaces(); + num = ifs.length; + for (i = 0; i < num; ++i) + getFields(alist, ifs[i]); + } + catch (NotFoundException e) {} + + CtMember.Cache memCache = ((CtClassType)cc).getMembers(); + CtMember field = memCache.fieldHead(); + CtMember tail = memCache.lastField(); + while (field != tail) { + field = field.next(); + if (!Modifier.isPrivate(field.getModifiers())) + alist.add(field); + } + } + + public CtField getField(String name, String desc) throws NotFoundException { + CtField f = getField2(name, desc); + return checkGetField(f, name, desc); + } + + private CtField checkGetField(CtField f, String name, String desc) + throws NotFoundException + { + if (f == null) { + String msg = "field: " + name; + if (desc != null) + msg += " type " + desc; + + throw new NotFoundException(msg + " in " + getName()); + } + else + return f; + } + + CtField getField2(String name, String desc) { + CtField df = getDeclaredField2(name, desc); + if (df != null) + return df; + + try { + CtClass[] ifs = getInterfaces(); + int num = ifs.length; + for (int i = 0; i < num; ++i) { + CtField f = ifs[i].getField2(name, desc); + if (f != null) + return f; + } + + CtClass s = getSuperclass(); + if (s != null) + return s.getField2(name, desc); + } + catch (NotFoundException e) {} + return null; + } + + public CtField[] getDeclaredFields() { + CtMember.Cache memCache = getMembers(); + CtMember field = memCache.fieldHead(); + CtMember tail = memCache.lastField(); + int num = CtMember.Cache.count(field, tail); + CtField[] cfs = new CtField[num]; + int i = 0; + while (field != tail) { + field = field.next(); + cfs[i++] = (CtField)field; + } + + return cfs; + } + + public CtField getDeclaredField(String name) throws NotFoundException { + return getDeclaredField(name, null); + } + + public CtField getDeclaredField(String name, String desc) throws NotFoundException { + CtField f = getDeclaredField2(name, desc); + return checkGetField(f, name, desc); + } + + private CtField getDeclaredField2(String name, String desc) { + CtMember.Cache memCache = getMembers(); + CtMember field = memCache.fieldHead(); + CtMember tail = memCache.lastField(); + while (field != tail) { + field = field.next(); + if (field.getName().equals(name) + && (desc == null || desc.equals(field.getSignature()))) + return (CtField)field; + } + + return null; + } + + public CtBehavior[] getDeclaredBehaviors() { + CtMember.Cache memCache = getMembers(); + CtMember cons = memCache.consHead(); + CtMember consTail = memCache.lastCons(); + int cnum = CtMember.Cache.count(cons, consTail); + CtMember mth = memCache.methodHead(); + CtMember mthTail = memCache.lastMethod(); + int mnum = CtMember.Cache.count(mth, mthTail); + + CtBehavior[] cb = new CtBehavior[cnum + mnum]; + int i = 0; + while (cons != consTail) { + cons = cons.next(); + cb[i++] = (CtBehavior)cons; + } + + while (mth != mthTail) { + mth = mth.next(); + cb[i++] = (CtBehavior)mth; + } + + return cb; + } + + public CtConstructor[] getConstructors() { + CtMember.Cache memCache = getMembers(); + CtMember cons = memCache.consHead(); + CtMember consTail = memCache.lastCons(); + + int n = 0; + CtMember mem = cons; + while (mem != consTail) { + mem = mem.next(); + if (isPubCons((CtConstructor)mem)) + n++; + } + + CtConstructor[] result = new CtConstructor[n]; + int i = 0; + mem = cons; + while (mem != consTail) { + mem = mem.next(); + CtConstructor cc = (CtConstructor)mem; + if (isPubCons(cc)) + result[i++] = cc; + } + + return result; + } + + private static boolean isPubCons(CtConstructor cons) { + return !Modifier.isPrivate(cons.getModifiers()) + && cons.isConstructor(); + } + + public CtConstructor getConstructor(String desc) + throws NotFoundException + { + CtMember.Cache memCache = getMembers(); + CtMember cons = memCache.consHead(); + CtMember consTail = memCache.lastCons(); + + while (cons != consTail) { + cons = cons.next(); + CtConstructor cc = (CtConstructor)cons; + if (cc.getMethodInfo2().getDescriptor().equals(desc) + && cc.isConstructor()) + return cc; + } + + return super.getConstructor(desc); + } + + public CtConstructor[] getDeclaredConstructors() { + CtMember.Cache memCache = getMembers(); + CtMember cons = memCache.consHead(); + CtMember consTail = memCache.lastCons(); + + int n = 0; + CtMember mem = cons; + while (mem != consTail) { + mem = mem.next(); + CtConstructor cc = (CtConstructor)mem; + if (cc.isConstructor()) + n++; + } + + CtConstructor[] result = new CtConstructor[n]; + int i = 0; + mem = cons; + while (mem != consTail) { + mem = mem.next(); + CtConstructor cc = (CtConstructor)mem; + if (cc.isConstructor()) + result[i++] = cc; + } + + return result; + } + + public CtConstructor getClassInitializer() { + CtMember.Cache memCache = getMembers(); + CtMember cons = memCache.consHead(); + CtMember consTail = memCache.lastCons(); + + while (cons != consTail) { + cons = cons.next(); + CtConstructor cc = (CtConstructor)cons; + if (cc.isClassInitializer()) + return cc; + } + + return null; + } + + public CtMethod[] getMethods() { + HashMap h = new HashMap(); + getMethods0(h, this); + return (CtMethod[])h.values().toArray(new CtMethod[h.size()]); + } + + private static void getMethods0(HashMap h, CtClass cc) { + try { + CtClass[] ifs = cc.getInterfaces(); + int size = ifs.length; + for (int i = 0; i < size; ++i) + getMethods0(h, ifs[i]); + } + catch (NotFoundException e) {} + + try { + CtClass s = cc.getSuperclass(); + if (s != null) + getMethods0(h, s); + } + catch (NotFoundException e) {} + + if (cc instanceof CtClassType) { + CtMember.Cache memCache = ((CtClassType)cc).getMembers(); + CtMember mth = memCache.methodHead(); + CtMember mthTail = memCache.lastMethod(); + + while (mth != mthTail) { + mth = mth.next(); + if (!Modifier.isPrivate(mth.getModifiers())) + h.put(((CtMethod)mth).getStringRep(), mth); + } + } + } + + public CtMethod getMethod(String name, String desc) + throws NotFoundException + { + CtMethod m = getMethod0(this, name, desc); + if (m != null) + return m; + else + throw new NotFoundException(name + "(..) is not found in " + + getName()); + } + + private static CtMethod getMethod0(CtClass cc, + String name, String desc) { + if (cc instanceof CtClassType) { + CtMember.Cache memCache = ((CtClassType)cc).getMembers(); + CtMember mth = memCache.methodHead(); + CtMember mthTail = memCache.lastMethod(); + + while (mth != mthTail) { + mth = mth.next(); + if (mth.getName().equals(name) + && ((CtMethod)mth).getMethodInfo2().getDescriptor().equals(desc)) + return (CtMethod)mth; + } + } + + try { + CtClass s = cc.getSuperclass(); + if (s != null) { + CtMethod m = getMethod0(s, name, desc); + if (m != null) + return m; + } + } + catch (NotFoundException e) {} + + try { + CtClass[] ifs = cc.getInterfaces(); + int size = ifs.length; + for (int i = 0; i < size; ++i) { + CtMethod m = getMethod0(ifs[i], name, desc); + if (m != null) + return m; + } + } + catch (NotFoundException e) {} + return null; + } + + public CtMethod[] getDeclaredMethods() { + CtMember.Cache memCache = getMembers(); + CtMember mth = memCache.methodHead(); + CtMember mthTail = memCache.lastMethod(); + int num = CtMember.Cache.count(mth, mthTail); + CtMethod[] cms = new CtMethod[num]; + int i = 0; + while (mth != mthTail) { + mth = mth.next(); + cms[i++] = (CtMethod)mth; + } + + return cms; + } + + public CtMethod getDeclaredMethod(String name) throws NotFoundException { + CtMember.Cache memCache = getMembers(); + CtMember mth = memCache.methodHead(); + CtMember mthTail = memCache.lastMethod(); + while (mth != mthTail) { + mth = mth.next(); + if (mth.getName().equals(name)) + return (CtMethod)mth; + } + + throw new NotFoundException(name + "(..) is not found in " + + getName()); + } + + public CtMethod getDeclaredMethod(String name, CtClass[] params) + throws NotFoundException + { + String desc = Descriptor.ofParameters(params); + CtMember.Cache memCache = getMembers(); + CtMember mth = memCache.methodHead(); + CtMember mthTail = memCache.lastMethod(); + + while (mth != mthTail) { + mth = mth.next(); + if (mth.getName().equals(name) + && ((CtMethod)mth).getMethodInfo2().getDescriptor().startsWith(desc)) + return (CtMethod)mth; + } + + throw new NotFoundException(name + "(..) is not found in " + + getName()); + } + + public void addField(CtField f, String init) + throws CannotCompileException + { + addField(f, CtField.Initializer.byExpr(init)); + } + + public void addField(CtField f, CtField.Initializer init) + throws CannotCompileException + { + checkModify(); + if (f.getDeclaringClass() != this) + throw new CannotCompileException("cannot add"); + + if (init == null) + init = f.getInit(); + + if (init != null) { + init.check(f.getSignature()); + int mod = f.getModifiers(); + if (Modifier.isStatic(mod) && Modifier.isFinal(mod)) + try { + ConstPool cp = getClassFile2().getConstPool(); + int index = init.getConstantValue(cp, f.getType()); + if (index != 0) { + f.getFieldInfo2().addAttribute(new ConstantAttribute(cp, index)); + init = null; + } + } + catch (NotFoundException e) {} + } + + getMembers().addField(f); + getClassFile2().addField(f.getFieldInfo2()); + + if (init != null) { + FieldInitLink fil = new FieldInitLink(f, init); + FieldInitLink link = fieldInitializers; + if (link == null) + fieldInitializers = fil; + else { + while (link.next != null) + link = link.next; + + link.next = fil; + } + } + } + + public void removeField(CtField f) throws NotFoundException { + checkModify(); + FieldInfo fi = f.getFieldInfo2(); + ClassFile cf = getClassFile2(); + if (cf.getFields().remove(fi)) { + getMembers().remove(f); + gcConstPool = true; + } + else + throw new NotFoundException(f.toString()); + } + + public CtConstructor makeClassInitializer() + throws CannotCompileException + { + CtConstructor clinit = getClassInitializer(); + if (clinit != null) + return clinit; + + checkModify(); + ClassFile cf = getClassFile2(); + Bytecode code = new Bytecode(cf.getConstPool(), 0, 0); + modifyClassConstructor(cf, code, 0, 0); + return getClassInitializer(); + } + + public void addConstructor(CtConstructor c) + throws CannotCompileException + { + checkModify(); + if (c.getDeclaringClass() != this) + throw new CannotCompileException("cannot add"); + + getMembers().addConstructor(c); + getClassFile2().addMethod(c.getMethodInfo2()); + } + + public void removeConstructor(CtConstructor m) throws NotFoundException { + checkModify(); + MethodInfo mi = m.getMethodInfo2(); + ClassFile cf = getClassFile2(); + if (cf.getMethods().remove(mi)) { + getMembers().remove(m); + gcConstPool = true; + } + else + throw new NotFoundException(m.toString()); + } + + public void addMethod(CtMethod m) throws CannotCompileException { + checkModify(); + if (m.getDeclaringClass() != this) + throw new CannotCompileException("bad declaring class"); + + int mod = m.getModifiers(); + if ((getModifiers() & Modifier.INTERFACE) != 0) { + m.setModifiers(mod | Modifier.PUBLIC); + if ((mod & Modifier.ABSTRACT) == 0) + throw new CannotCompileException( + "an interface method must be abstract: " + m.toString()); + } + + getMembers().addMethod(m); + getClassFile2().addMethod(m.getMethodInfo2()); + if ((mod & Modifier.ABSTRACT) != 0) + setModifiers(getModifiers() | Modifier.ABSTRACT); + } + + public void removeMethod(CtMethod m) throws NotFoundException { + checkModify(); + MethodInfo mi = m.getMethodInfo2(); + ClassFile cf = getClassFile2(); + if (cf.getMethods().remove(mi)) { + getMembers().remove(m); + gcConstPool = true; + } + else + throw new NotFoundException(m.toString()); + } + + public byte[] getAttribute(String name) { + AttributeInfo ai = getClassFile2().getAttribute(name); + if (ai == null) + return null; + else + return ai.get(); + } + + public void setAttribute(String name, byte[] data) { + checkModify(); + ClassFile cf = getClassFile2(); + cf.addAttribute(new AttributeInfo(cf.getConstPool(), name, data)); + } + + public void instrument(CodeConverter converter) + throws CannotCompileException + { + checkModify(); + ClassFile cf = getClassFile2(); + ConstPool cp = cf.getConstPool(); + List list = cf.getMethods(); + int n = list.size(); + for (int i = 0; i < n; ++i) { + MethodInfo minfo = (MethodInfo)list.get(i); + converter.doit(this, minfo, cp); + } + } + + public void instrument(ExprEditor editor) + throws CannotCompileException + { + checkModify(); + ClassFile cf = getClassFile2(); + List list = cf.getMethods(); + int n = list.size(); + for (int i = 0; i < n; ++i) { + MethodInfo minfo = (MethodInfo)list.get(i); + editor.doit(this, minfo); + } + } + + /** + * @see javassist.CtClass#prune() + * @see javassist.CtClass#stopPruning(boolean) + */ + public void prune() { + if (wasPruned) + return; + + wasPruned = wasFrozen = true; + getClassFile2().prune(); + } + + public void rebuildClassFile() { gcConstPool = true; } + + public void toBytecode(DataOutputStream out) + throws CannotCompileException, IOException + { + try { + if (isModified()) { + checkPruned("toBytecode"); + ClassFile cf = getClassFile2(); + if (gcConstPool) { + cf.compact(); + gcConstPool = false; + } + + modifyClassConstructor(cf); + modifyConstructors(cf); + cf.write(out); + out.flush(); + fieldInitializers = null; + if (doPruning) { + // to save memory + cf.prune(); + wasPruned = true; + } + } + else { + classPool.writeClassfile(getName(), out); + // to save memory + // classfile = null; + } + + getCount = 0; + wasFrozen = true; + } + catch (NotFoundException e) { + throw new CannotCompileException(e); + } + catch (IOException e) { + throw new CannotCompileException(e); + } + } + + /* See also checkModified() + */ + private void checkPruned(String method) { + if (wasPruned) + throw new RuntimeException(method + "(): " + getName() + + " was pruned."); + } + + public boolean stopPruning(boolean stop) { + boolean prev = !doPruning; + doPruning = !stop; + return prev; + } + + private void modifyClassConstructor(ClassFile cf) + throws CannotCompileException, NotFoundException + { + if (fieldInitializers == null) + return; + + Bytecode code = new Bytecode(cf.getConstPool(), 0, 0); + Javac jv = new Javac(code, this); + int stacksize = 0; + boolean doInit = false; + for (FieldInitLink fi = fieldInitializers; fi != null; fi = fi.next) { + CtField f = fi.field; + if (Modifier.isStatic(f.getModifiers())) { + doInit = true; + int s = fi.init.compileIfStatic(f.getType(), f.getName(), + code, jv); + if (stacksize < s) + stacksize = s; + } + } + + if (doInit) // need an initializer for static fileds. + modifyClassConstructor(cf, code, stacksize, 0); + } + + private void modifyClassConstructor(ClassFile cf, Bytecode code, + int stacksize, int localsize) + throws CannotCompileException + { + MethodInfo m = cf.getStaticInitializer(); + if (m == null) { + code.add(Bytecode.RETURN); + code.setMaxStack(stacksize); + code.setMaxLocals(localsize); + m = new MethodInfo(cf.getConstPool(), "<clinit>", "()V"); + m.setAccessFlags(AccessFlag.STATIC); + m.setCodeAttribute(code.toCodeAttribute()); + cf.addMethod(m); + CtMember.Cache cache = hasMemberCache(); + if (cache != null) + cache.addConstructor(new CtConstructor(m, this)); + } + else { + CodeAttribute codeAttr = m.getCodeAttribute(); + if (codeAttr == null) + throw new CannotCompileException("empty <clinit>"); + + try { + CodeIterator it = codeAttr.iterator(); + int pos = it.insertEx(code.get()); + it.insert(code.getExceptionTable(), pos); + int maxstack = codeAttr.getMaxStack(); + if (maxstack < stacksize) + codeAttr.setMaxStack(stacksize); + + int maxlocals = codeAttr.getMaxLocals(); + if (maxlocals < localsize) + codeAttr.setMaxLocals(localsize); + } + catch (BadBytecode e) { + throw new CannotCompileException(e); + } + } + + try { + m.rebuildStackMapIf6(classPool, cf); + } + catch (BadBytecode e) { + throw new CannotCompileException(e); + } + } + + private void modifyConstructors(ClassFile cf) + throws CannotCompileException, NotFoundException + { + if (fieldInitializers == null) + return; + + ConstPool cp = cf.getConstPool(); + List list = cf.getMethods(); + int n = list.size(); + for (int i = 0; i < n; ++i) { + MethodInfo minfo = (MethodInfo)list.get(i); + if (minfo.isConstructor()) { + CodeAttribute codeAttr = minfo.getCodeAttribute(); + if (codeAttr != null) + try { + Bytecode init = new Bytecode(cp, 0, + codeAttr.getMaxLocals()); + CtClass[] params + = Descriptor.getParameterTypes( + minfo.getDescriptor(), + classPool); + int stacksize = makeFieldInitializer(init, params); + insertAuxInitializer(codeAttr, init, stacksize); + minfo.rebuildStackMapIf6(classPool, cf); + } + catch (BadBytecode e) { + throw new CannotCompileException(e); + } + } + } + } + + private static void insertAuxInitializer(CodeAttribute codeAttr, + Bytecode initializer, + int stacksize) + throws BadBytecode + { + CodeIterator it = codeAttr.iterator(); + int index = it.skipSuperConstructor(); + if (index < 0) { + index = it.skipThisConstructor(); + if (index >= 0) + return; // this() is called. + + // Neither this() or super() is called. + } + + int pos = it.insertEx(initializer.get()); + it.insert(initializer.getExceptionTable(), pos); + int maxstack = codeAttr.getMaxStack(); + if (maxstack < stacksize) + codeAttr.setMaxStack(stacksize); + } + + private int makeFieldInitializer(Bytecode code, CtClass[] parameters) + throws CannotCompileException, NotFoundException + { + int stacksize = 0; + Javac jv = new Javac(code, this); + try { + jv.recordParams(parameters, false); + } + catch (CompileError e) { + throw new CannotCompileException(e); + } + + for (FieldInitLink fi = fieldInitializers; fi != null; fi = fi.next) { + CtField f = fi.field; + if (!Modifier.isStatic(f.getModifiers())) { + int s = fi.init.compile(f.getType(), f.getName(), code, + parameters, jv); + if (stacksize < s) + stacksize = s; + } + } + + return stacksize; + } + + // Methods used by CtNewWrappedMethod + + Hashtable getHiddenMethods() { + if (hiddenMethods == null) + hiddenMethods = new Hashtable(); + + return hiddenMethods; + } + + int getUniqueNumber() { return uniqueNumberSeed++; } + + public String makeUniqueName(String prefix) { + HashMap table = new HashMap(); + makeMemberList(table); + Set keys = table.keySet(); + String[] methods = new String[keys.size()]; + keys.toArray(methods); + + if (notFindInArray(prefix, methods)) + return prefix; + + int i = 100; + String name; + do { + if (i > 999) + throw new RuntimeException("too many unique name"); + + name = prefix + i++; + } while (!notFindInArray(name, methods)); + return name; + } + + private static boolean notFindInArray(String prefix, String[] values) { + int len = values.length; + for (int i = 0; i < len; i++) + if (values[i].startsWith(prefix)) + return false; + + return true; + } + + private void makeMemberList(HashMap table) { + int mod = getModifiers(); + if (Modifier.isAbstract(mod) || Modifier.isInterface(mod)) + try { + CtClass[] ifs = getInterfaces(); + int size = ifs.length; + for (int i = 0; i < size; i++) { + CtClass ic =ifs[i]; + if (ic != null && ic instanceof CtClassType) + ((CtClassType)ic).makeMemberList(table); + } + } + catch (NotFoundException e) {} + + try { + CtClass s = getSuperclass(); + if (s != null && s instanceof CtClassType) + ((CtClassType)s).makeMemberList(table); + } + catch (NotFoundException e) {} + + List list = getClassFile2().getMethods(); + int n = list.size(); + for (int i = 0; i < n; i++) { + MethodInfo minfo = (MethodInfo)list.get(i); + table.put(minfo.getName(), this); + } + + list = getClassFile2().getFields(); + n = list.size(); + for (int i = 0; i < n; i++) { + FieldInfo finfo = (FieldInfo)list.get(i); + table.put(finfo.getName(), this); + } + } +} + +class FieldInitLink { + FieldInitLink next; + CtField field; + CtField.Initializer init; + + FieldInitLink(CtField f, CtField.Initializer i) { + next = null; + field = f; + init = i; + } +} diff --git a/src/main/javassist/CtConstructor.java b/src/main/javassist/CtConstructor.java new file mode 100644 index 0000000..a90b73c --- /dev/null +++ b/src/main/javassist/CtConstructor.java @@ -0,0 +1,402 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist; + +import javassist.bytecode.*; +import javassist.compiler.Javac; +import javassist.compiler.CompileError; + +/** + * An instance of CtConstructor represents a constructor. + * It may represent a static constructor + * (class initializer). To distinguish a constructor and a class + * initializer, call <code>isClassInitializer()</code>. + * + * <p>See the super class <code>CtBehavior</code> as well since + * a number of useful methods are in <code>CtBehavior</code>. + * + * @see CtClass#getDeclaredConstructors() + * @see CtClass#getClassInitializer() + * @see CtNewConstructor + */ +public final class CtConstructor extends CtBehavior { + protected CtConstructor(MethodInfo minfo, CtClass declaring) { + super(declaring, minfo); + } + + /** + * Creates a constructor with no constructor body. + * The created constructor + * must be added to a class with <code>CtClass.addConstructor()</code>. + * + * <p>The created constructor does not include a constructor body, + * which must be specified with <code>setBody()</code>. + * + * @param declaring the class to which the created method is added. + * @param parameters a list of the parameter types + * + * @see CtClass#addConstructor(CtConstructor) + * @see CtConstructor#setBody(String) + * @see CtConstructor#setBody(CtConstructor,ClassMap) + */ + public CtConstructor(CtClass[] parameters, CtClass declaring) { + this((MethodInfo)null, declaring); + ConstPool cp = declaring.getClassFile2().getConstPool(); + String desc = Descriptor.ofConstructor(parameters); + methodInfo = new MethodInfo(cp, "<init>", desc); + setModifiers(Modifier.PUBLIC); + } + + /** + * Creates a copy of a <code>CtConstructor</code> object. + * The created constructor must be + * added to a class with <code>CtClass.addConstructor()</code>. + * + * <p>All occurrences of class names in the created constructor + * are replaced with names specified by + * <code>map</code> if <code>map</code> is not <code>null</code>. + * + * <p>By default, all the occurrences of the names of the class + * declaring <code>src</code> and the superclass are replaced + * with the name of the class and the superclass that + * the created constructor is added to. + * This is done whichever <code>map</code> is null or not. + * To prevent this replacement, call <code>ClassMap.fix()</code> + * or <code>put()</code> to explicitly specify replacement. + * + * <p><b>Note:</b> if the <code>.class</code> notation (for example, + * <code>String.class</code>) is included in an expression, the + * Javac compiler may produce a helper method. + * Since this constructor never + * copies this helper method, the programmers have the responsiblity of + * copying it. Otherwise, use <code>Class.forName()</code> in the + * expression. + * + * @param src the source method. + * @param declaring the class to which the created method is added. + * @param map the hashtable associating original class names + * with substituted names. + * It can be <code>null</code>. + * + * @see CtClass#addConstructor(CtConstructor) + * @see ClassMap#fix(String) + */ + public CtConstructor(CtConstructor src, CtClass declaring, ClassMap map) + throws CannotCompileException + { + this((MethodInfo)null, declaring); + copy(src, true, map); + } + + /** + * Returns true if this object represents a constructor. + */ + public boolean isConstructor() { + return methodInfo.isConstructor(); + } + + /** + * Returns true if this object represents a static initializer. + */ + public boolean isClassInitializer() { + return methodInfo.isStaticInitializer(); + } + + /** + * Returns the constructor name followed by parameter types + * such as <code>javassist.CtConstructor(CtClass[],CtClass)</code>. + * + * @since 3.5 + */ + public String getLongName() { + return getDeclaringClass().getName() + + (isConstructor() ? Descriptor.toString(getSignature()) + : ("." + MethodInfo.nameClinit + "()")); + } + + /** + * Obtains the name of this constructor. + * It is the same as the simple name of the class declaring this + * constructor. If this object represents a class initializer, + * then this method returns <code>"<clinit>"</code>. + */ + public String getName() { + if (methodInfo.isStaticInitializer()) + return MethodInfo.nameClinit; + else + return declaringClass.getSimpleName(); + } + + /** + * Returns true if the constructor (or static initializer) + * is the default one. This method returns true if the constructor + * takes some arguments but it does not perform anything except + * calling <code>super()</code> (the no-argument constructor of + * the super class). + */ + public boolean isEmpty() { + CodeAttribute ca = getMethodInfo2().getCodeAttribute(); + if (ca == null) + return false; // native or abstract?? + // they are not allowed, though. + + ConstPool cp = ca.getConstPool(); + CodeIterator it = ca.iterator(); + try { + int pos, desc; + int op0 = it.byteAt(it.next()); + return op0 == Opcode.RETURN // empty static initializer + || (op0 == Opcode.ALOAD_0 + && it.byteAt(pos = it.next()) == Opcode.INVOKESPECIAL + && (desc = cp.isConstructor(getSuperclassName(), + it.u16bitAt(pos + 1))) != 0 + && "()V".equals(cp.getUtf8Info(desc)) + && it.byteAt(it.next()) == Opcode.RETURN + && !it.hasNext()); + } + catch (BadBytecode e) {} + return false; + } + + private String getSuperclassName() { + ClassFile cf = declaringClass.getClassFile2(); + return cf.getSuperclass(); + } + + /** + * Returns true if this constructor calls a constructor + * of the super class. This method returns false if it + * calls another constructor of this class by <code>this()</code>. + */ + public boolean callsSuper() throws CannotCompileException { + CodeAttribute codeAttr = methodInfo.getCodeAttribute(); + if (codeAttr != null) { + CodeIterator it = codeAttr.iterator(); + try { + int index = it.skipSuperConstructor(); + return index >= 0; + } + catch (BadBytecode e) { + throw new CannotCompileException(e); + } + } + + return false; + } + + /** + * Sets a constructor body. + * + * @param src the source code representing the constructor body. + * It must be a single statement or block. + * If it is <code>null</code>, the substituted + * constructor body does nothing except calling + * <code>super()</code>. + */ + public void setBody(String src) throws CannotCompileException { + if (src == null) + if (isClassInitializer()) + src = ";"; + else + src = "super();"; + + super.setBody(src); + } + + /** + * Copies a constructor body from another constructor. + * + * <p>All occurrences of the class names in the copied body + * are replaced with the names specified by + * <code>map</code> if <code>map</code> is not <code>null</code>. + * + * @param src the method that the body is copied from. + * @param map the hashtable associating original class names + * with substituted names. + * It can be <code>null</code>. + */ + public void setBody(CtConstructor src, ClassMap map) + throws CannotCompileException + { + setBody0(src.declaringClass, src.methodInfo, + declaringClass, methodInfo, map); + } + + /** + * Inserts bytecode just after another constructor in the super class + * or this class is called. + * It does not work if this object represents a class initializer. + * + * @param src the source code representing the inserted bytecode. + * It must be a single statement or block. + */ + public void insertBeforeBody(String src) throws CannotCompileException { + CtClass cc = declaringClass; + cc.checkModify(); + if (isClassInitializer()) + throw new CannotCompileException("class initializer"); + + CodeAttribute ca = methodInfo.getCodeAttribute(); + CodeIterator iterator = ca.iterator(); + Bytecode b = new Bytecode(methodInfo.getConstPool(), + ca.getMaxStack(), ca.getMaxLocals()); + b.setStackDepth(ca.getMaxStack()); + Javac jv = new Javac(b, cc); + try { + jv.recordParams(getParameterTypes(), false); + jv.compileStmnt(src); + ca.setMaxStack(b.getMaxStack()); + ca.setMaxLocals(b.getMaxLocals()); + iterator.skipConstructor(); + int pos = iterator.insertEx(b.get()); + iterator.insert(b.getExceptionTable(), pos); + methodInfo.rebuildStackMapIf6(cc.getClassPool(), cc.getClassFile2()); + } + catch (NotFoundException e) { + throw new CannotCompileException(e); + } + catch (CompileError e) { + throw new CannotCompileException(e); + } + catch (BadBytecode e) { + throw new CannotCompileException(e); + } + } + + /* This method is called by addCatch() in CtBehavior. + * super() and this() must not be in a try statement. + */ + int getStartPosOfBody(CodeAttribute ca) throws CannotCompileException { + CodeIterator ci = ca.iterator(); + try { + ci.skipConstructor(); + return ci.next(); + } + catch (BadBytecode e) { + throw new CannotCompileException(e); + } + } + + /** + * Makes a copy of this constructor and converts it into a method. + * The signature of the mehtod is the same as the that of this constructor. + * The return type is <code>void</code>. The resulting method must be + * appended to the class specified by <code>declaring</code>. + * If this constructor is a static initializer, the resulting method takes + * no parameter. + * + * <p>An occurrence of another constructor call <code>this()</code> + * or a super constructor call <code>super()</code> is + * eliminated from the resulting method. + * + * <p>The immediate super class of the class declaring this constructor + * must be also a super class of the class declaring the resulting method. + * If the constructor accesses a field, the class declaring the resulting method + * must also declare a field with the same name and type. + * + * @param name the name of the resulting method. + * @param declaring the class declaring the resulting method. + */ + public CtMethod toMethod(String name, CtClass declaring) + throws CannotCompileException + { + return toMethod(name, declaring, null); + } + + /** + * Makes a copy of this constructor and converts it into a method. + * The signature of the method is the same as the that of this constructor. + * The return type is <code>void</code>. The resulting method must be + * appended to the class specified by <code>declaring</code>. + * If this constructor is a static initializer, the resulting method takes + * no parameter. + * + * <p>An occurrence of another constructor call <code>this()</code> + * or a super constructor call <code>super()</code> is + * eliminated from the resulting method. + * + * <p>The immediate super class of the class declaring this constructor + * must be also a super class of the class declaring the resulting method + * (this is obviously true if the second parameter <code>declaring</code> is + * the same as the class declaring this constructor). + * If the constructor accesses a field, the class declaring the resulting method + * must also declare a field with the same name and type. + * + * @param name the name of the resulting method. + * @param declaring the class declaring the resulting method. + * It is normally the same as the class declaring this + * constructor. + * @param map the hash table associating original class names + * with substituted names. The original class names will be + * replaced while making a copy. + * <code>map</code> can be <code>null</code>. + */ + public CtMethod toMethod(String name, CtClass declaring, ClassMap map) + throws CannotCompileException + { + CtMethod method = new CtMethod(null, declaring); + method.copy(this, false, map); + if (isConstructor()) { + MethodInfo minfo = method.getMethodInfo2(); + CodeAttribute ca = minfo.getCodeAttribute(); + if (ca != null) { + removeConsCall(ca); + try { + methodInfo.rebuildStackMapIf6(declaring.getClassPool(), + declaring.getClassFile2()); + } + catch (BadBytecode e) { + throw new CannotCompileException(e); + } + } + } + + method.setName(name); + return method; + } + + private static void removeConsCall(CodeAttribute ca) + throws CannotCompileException + { + CodeIterator iterator = ca.iterator(); + try { + int pos = iterator.skipConstructor(); + if (pos >= 0) { + int mref = iterator.u16bitAt(pos + 1); + String desc = ca.getConstPool().getMethodrefType(mref); + int num = Descriptor.numOfParameters(desc) + 1; + if (num > 3) + pos = iterator.insertGapAt(pos, num - 3, false).position; + + iterator.writeByte(Opcode.POP, pos++); // this + iterator.writeByte(Opcode.NOP, pos); + iterator.writeByte(Opcode.NOP, pos + 1); + Descriptor.Iterator it = new Descriptor.Iterator(desc); + while (true) { + it.next(); + if (it.isParameter()) + iterator.writeByte(it.is2byte() ? Opcode.POP2 : Opcode.POP, + pos++); + else + break; + } + } + } + catch (BadBytecode e) { + throw new CannotCompileException(e); + } + } +} diff --git a/src/main/javassist/CtField.java b/src/main/javassist/CtField.java new file mode 100644 index 0000000..c4af7e5 --- /dev/null +++ b/src/main/javassist/CtField.java @@ -0,0 +1,1389 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist; + +import javassist.bytecode.*; +import javassist.compiler.Javac; +import javassist.compiler.SymbolTable; +import javassist.compiler.CompileError; +import javassist.compiler.ast.ASTree; +import javassist.compiler.ast.IntConst; +import javassist.compiler.ast.DoubleConst; +import javassist.compiler.ast.StringL; + +/** + * An instance of CtField represents a field. + * + * @see CtClass#getDeclaredFields() + */ +public class CtField extends CtMember { + static final String javaLangString = "java.lang.String"; + + protected FieldInfo fieldInfo; + + /** + * Creates a <code>CtField</code> object. + * The created field must be added to a class + * with <code>CtClass.addField()</code>. + * An initial value of the field is specified + * by a <code>CtField.Initializer</code> object. + * + * <p>If getter and setter methods are needed, + * call <code>CtNewMethod.getter()</code> and + * <code>CtNewMethod.setter()</code>. + * + * @param type field type + * @param name field name + * @param declaring the class to which the field will be added. + * + * @see CtClass#addField(CtField) + * @see CtNewMethod#getter(String,CtField) + * @see CtNewMethod#setter(String,CtField) + * @see CtField.Initializer + */ + public CtField(CtClass type, String name, CtClass declaring) + throws CannotCompileException + { + this(Descriptor.of(type), name, declaring); + } + + /** + * Creates a copy of the given field. + * The created field must be added to a class + * with <code>CtClass.addField()</code>. + * An initial value of the field is specified + * by a <code>CtField.Initializer</code> object. + * + * <p>If getter and setter methods are needed, + * call <code>CtNewMethod.getter()</code> and + * <code>CtNewMethod.setter()</code>. + * + * @param src the original field + * @param declaring the class to which the field will be added. + * @see CtNewMethod#getter(String,CtField) + * @see CtNewMethod#setter(String,CtField) + * @see CtField.Initializer + */ + public CtField(CtField src, CtClass declaring) + throws CannotCompileException + { + this(src.fieldInfo.getDescriptor(), src.fieldInfo.getName(), + declaring); + java.util.ListIterator iterator + = src.fieldInfo.getAttributes().listIterator(); + FieldInfo fi = fieldInfo; + fi.setAccessFlags(src.fieldInfo.getAccessFlags()); + ConstPool cp = fi.getConstPool(); + while (iterator.hasNext()) { + AttributeInfo ainfo = (AttributeInfo)iterator.next(); + fi.addAttribute(ainfo.copy(cp, null)); + } + } + + private CtField(String typeDesc, String name, CtClass clazz) + throws CannotCompileException + { + super(clazz); + ClassFile cf = clazz.getClassFile2(); + if (cf == null) + throw new CannotCompileException("bad declaring class: " + + clazz.getName()); + + fieldInfo = new FieldInfo(cf.getConstPool(), name, typeDesc); + } + + CtField(FieldInfo fi, CtClass clazz) { + super(clazz); + fieldInfo = fi; + } + + /** + * Returns a String representation of the object. + */ + public String toString() { + return getDeclaringClass().getName() + "." + getName() + + ":" + fieldInfo.getDescriptor(); + } + + protected void extendToString(StringBuffer buffer) { + buffer.append(' '); + buffer.append(getName()); + buffer.append(' '); + buffer.append(fieldInfo.getDescriptor()); + } + + /* Javac.CtFieldWithInit overrides. + */ + protected ASTree getInitAST() { return null; } + + /* Called by CtClassType.addField(). + */ + Initializer getInit() { + ASTree tree = getInitAST(); + if (tree == null) + return null; + else + return Initializer.byExpr(tree); + } + + /** + * Compiles the given source code and creates a field. + * Examples of the source code are: + * + * <ul><pre> + * "public String name;" + * "public int k = 3;"</pre></ul> + * + * <p>Note that the source code ends with <code>';'</code> + * (semicolon). + * + * @param src the source text. + * @param declaring the class to which the created field is added. + */ + public static CtField make(String src, CtClass declaring) + throws CannotCompileException + { + Javac compiler = new Javac(declaring); + try { + CtMember obj = compiler.compile(src); + if (obj instanceof CtField) + return (CtField)obj; // an instance of Javac.CtFieldWithInit + } + catch (CompileError e) { + throw new CannotCompileException(e); + } + + throw new CannotCompileException("not a field"); + } + + /** + * Returns the FieldInfo representing the field in the class file. + */ + public FieldInfo getFieldInfo() { + declaringClass.checkModify(); + return fieldInfo; + } + + /** + * Returns the FieldInfo representing the field in the class + * file (read only). + * Normal applications do not need calling this method. Use + * <code>getFieldInfo()</code>. + * + * <p>The <code>FieldInfo</code> object obtained by this method + * is read only. Changes to this object might not be reflected + * on a class file generated by <code>toBytecode()</code>, + * <code>toClass()</code>, etc in <code>CtClass</code>. + * + * <p>This method is available even if the <code>CtClass</code> + * containing this field is frozen. However, if the class is + * frozen, the <code>FieldInfo</code> might be also pruned. + * + * @see #getFieldInfo() + * @see CtClass#isFrozen() + * @see CtClass#prune() + */ + public FieldInfo getFieldInfo2() { return fieldInfo; } + + /** + * Returns the class declaring the field. + */ + public CtClass getDeclaringClass() { + // this is redundant but for javadoc. + return super.getDeclaringClass(); + } + + /** + * Returns the name of the field. + */ + public String getName() { + return fieldInfo.getName(); + } + + /** + * Changes the name of the field. + */ + public void setName(String newName) { + declaringClass.checkModify(); + fieldInfo.setName(newName); + } + + /** + * Returns the encoded modifiers of the field. + * + * @see Modifier + */ + public int getModifiers() { + return AccessFlag.toModifier(fieldInfo.getAccessFlags()); + } + + /** + * Sets the encoded modifiers of the field. + * + * @see Modifier + */ + public void setModifiers(int mod) { + declaringClass.checkModify(); + fieldInfo.setAccessFlags(AccessFlag.of(mod)); + } + + /** + * Returns true if the class has the specified annotation class. + * + * @param clz the annotation class. + * @return <code>true</code> if the annotation is found, otherwise <code>false</code>. + * @since 3.11 + */ + public boolean hasAnnotation(Class clz) { + FieldInfo fi = getFieldInfo2(); + AnnotationsAttribute ainfo = (AnnotationsAttribute) + fi.getAttribute(AnnotationsAttribute.invisibleTag); + AnnotationsAttribute ainfo2 = (AnnotationsAttribute) + fi.getAttribute(AnnotationsAttribute.visibleTag); + return CtClassType.hasAnnotationType(clz, getDeclaringClass().getClassPool(), + ainfo, ainfo2); + } + + /** + * Returns the annotation if the class has the specified annotation class. + * For example, if an annotation <code>@Author</code> is associated + * with this field, an <code>Author</code> object is returned. + * The member values can be obtained by calling methods on + * the <code>Author</code> object. + * + * @param clz the annotation class. + * @return the annotation if found, otherwise <code>null</code>. + * @since 3.11 + */ + public Object getAnnotation(Class clz) throws ClassNotFoundException { + FieldInfo fi = getFieldInfo2(); + AnnotationsAttribute ainfo = (AnnotationsAttribute) + fi.getAttribute(AnnotationsAttribute.invisibleTag); + AnnotationsAttribute ainfo2 = (AnnotationsAttribute) + fi.getAttribute(AnnotationsAttribute.visibleTag); + return CtClassType.getAnnotationType(clz, getDeclaringClass().getClassPool(), + ainfo, ainfo2); + } + + /** + * Returns the annotations associated with this field. + * + * @return an array of annotation-type objects. + * @see #getAvailableAnnotations() + * @since 3.1 + */ + public Object[] getAnnotations() throws ClassNotFoundException { + return getAnnotations(false); + } + + /** + * Returns the annotations associated with this field. + * If any annotations are not on the classpath, they are not included + * in the returned array. + * + * @return an array of annotation-type objects. + * @see #getAnnotations() + * @since 3.3 + */ + public Object[] getAvailableAnnotations(){ + try { + return getAnnotations(true); + } + catch (ClassNotFoundException e) { + throw new RuntimeException("Unexpected exception", e); + } + } + + private Object[] getAnnotations(boolean ignoreNotFound) throws ClassNotFoundException { + FieldInfo fi = getFieldInfo2(); + AnnotationsAttribute ainfo = (AnnotationsAttribute) + fi.getAttribute(AnnotationsAttribute.invisibleTag); + AnnotationsAttribute ainfo2 = (AnnotationsAttribute) + fi.getAttribute(AnnotationsAttribute.visibleTag); + return CtClassType.toAnnotationType(ignoreNotFound, getDeclaringClass().getClassPool(), + ainfo, ainfo2); + } + + /** + * Returns the character string representing the type of the field. + * The field signature is represented by a character string + * called a field descriptor, which is defined in the JVM specification. + * If two fields have the same type, + * <code>getSignature()</code> returns the same string. + * + * <p>Note that the returned string is not the type signature + * contained in the <code>SignatureAttirbute</code>. It is + * a descriptor. To obtain a type signature, call the following + * methods: + * + * <ul><pre>getFieldInfo().getAttribute(SignatureAttribute.tag) + * </pre></ul> + * + * @see javassist.bytecode.Descriptor + * @see javassist.bytecode.SignatureAttribute + */ + public String getSignature() { + return fieldInfo.getDescriptor(); + } + + /** + * Returns the type of the field. + */ + public CtClass getType() throws NotFoundException { + return Descriptor.toCtClass(fieldInfo.getDescriptor(), + declaringClass.getClassPool()); + } + + /** + * Sets the type of the field. + */ + public void setType(CtClass clazz) { + declaringClass.checkModify(); + fieldInfo.setDescriptor(Descriptor.of(clazz)); + } + + /** + * Returns the value of this field if it is a constant field. + * This method works only if the field type is a primitive type + * or <code>String</code> type. Otherwise, it returns <code>null</code>. + * A constant field is <code>static</code> and <code>final</code>. + * + * @return a <code>Integer</code>, <code>Long</code>, <code>Float</code>, + * <code>Double</code>, <code>Boolean</code>, + * or <code>String</code> object + * representing the constant value. + * <code>null</code> if it is not a constant field + * or if the field type is not a primitive type + * or <code>String</code>. + */ + public Object getConstantValue() { + // When this method is modified, + // see also getConstantFieldValue() in TypeChecker. + + int index = fieldInfo.getConstantValue(); + if (index == 0) + return null; + + ConstPool cp = fieldInfo.getConstPool(); + switch (cp.getTag(index)) { + case ConstPool.CONST_Long : + return new Long(cp.getLongInfo(index)); + case ConstPool.CONST_Float : + return new Float(cp.getFloatInfo(index)); + case ConstPool.CONST_Double : + return new Double(cp.getDoubleInfo(index)); + case ConstPool.CONST_Integer : + int value = cp.getIntegerInfo(index); + // "Z" means boolean type. + if ("Z".equals(fieldInfo.getDescriptor())) + return new Boolean(value != 0); + else + return new Integer(value); + case ConstPool.CONST_String : + return cp.getStringInfo(index); + default : + throw new RuntimeException("bad tag: " + cp.getTag(index) + + " at " + index); + } + } + + /** + * Obtains an attribute with the given name. + * If that attribute is not found in the class file, this + * method returns null. + * + * <p>Note that an attribute is a data block specified by + * the class file format. + * See {@link javassist.bytecode.AttributeInfo}. + * + * @param name attribute name + */ + public byte[] getAttribute(String name) { + AttributeInfo ai = fieldInfo.getAttribute(name); + if (ai == null) + return null; + else + return ai.get(); + } + + /** + * Adds an attribute. The attribute is saved in the class file. + * + * <p>Note that an attribute is a data block specified by + * the class file format. + * See {@link javassist.bytecode.AttributeInfo}. + * + * @param name attribute name + * @param data attribute value + */ + public void setAttribute(String name, byte[] data) { + declaringClass.checkModify(); + fieldInfo.addAttribute(new AttributeInfo(fieldInfo.getConstPool(), + name, data)); + } + + // inner classes + + /** + * Instances of this class specify how to initialize a field. + * <code>Initializer</code> is passed to + * <code>CtClass.addField()</code> with a <code>CtField</code>. + * + * <p>This class cannot be instantiated with the <code>new</code> operator. + * Factory methods such as <code>byParameter()</code> and + * <code>byNew</code> + * must be used for the instantiation. They create a new instance with + * the given parameters and return it. + * + * @see CtClass#addField(CtField,CtField.Initializer) + */ + public static abstract class Initializer { + /** + * Makes an initializer that assigns a constant integer value. + * The field must be integer, short, char, or byte type. + */ + public static Initializer constant(int i) { + return new IntInitializer(i); + } + + /** + * Makes an initializer that assigns a constant boolean value. + * The field must be boolean type. + */ + public static Initializer constant(boolean b) { + return new IntInitializer(b ? 1 : 0); + } + + /** + * Makes an initializer that assigns a constant long value. + * The field must be long type. + */ + public static Initializer constant(long l) { + return new LongInitializer(l); + } + + /** + * Makes an initializer that assigns a constant float value. + * The field must be float type. + */ + public static Initializer constant(float l) { + return new FloatInitializer(l); + } + + /** + * Makes an initializer that assigns a constant double value. + * The field must be double type. + */ + public static Initializer constant(double d) { + return new DoubleInitializer(d); + } + + /** + * Makes an initializer that assigns a constant string value. + * The field must be <code>java.lang.String</code> type. + */ + public static Initializer constant(String s) { + return new StringInitializer(s); + } + + /** + * Makes an initializer using a constructor parameter. + * + * <p>The initial value is the + * N-th parameter given to the constructor of the object including + * the field. If the constructor takes less than N parameters, + * the field is not initialized. + * If the field is static, it is never initialized. + * + * @param nth the n-th (>= 0) parameter is used as + * the initial value. + * If nth is 0, then the first parameter is + * used. + */ + public static Initializer byParameter(int nth) { + ParamInitializer i = new ParamInitializer(); + i.nthParam = nth; + return i; + } + + /** + * Makes an initializer creating a new object. + * + * <p>This initializer creates a new object and uses it as the initial + * value of the field. The constructor of the created object receives + * the parameter: + * + * <ul><code>Object obj</code> - the object including the field.<br> + * </ul> + * + * <p>If the initialized field is static, then the constructor does + * not receive any parameters. + * + * @param objectType the class instantiated for the initial value. + */ + public static Initializer byNew(CtClass objectType) { + NewInitializer i = new NewInitializer(); + i.objectType = objectType; + i.stringParams = null; + i.withConstructorParams = false; + return i; + } + + /** + * Makes an initializer creating a new object. + * + * <p>This initializer creates a new object and uses it as the initial + * value of the field. The constructor of the created object receives + * the parameters: + * + * <ul><code>Object obj</code> - the object including the field.<br> + * <code>String[] strs</code> - the character strings specified + * by <code>stringParams</code><br> + * </ul> + * + * <p>If the initialized field is static, then the constructor + * receives only <code>strs</code>. + * + * @param objectType the class instantiated for the initial value. + * @param stringParams the array of strings passed to the + * constructor. + */ + public static Initializer byNew(CtClass objectType, + String[] stringParams) { + NewInitializer i = new NewInitializer(); + i.objectType = objectType; + i.stringParams = stringParams; + i.withConstructorParams = false; + return i; + } + + /** + * Makes an initializer creating a new object. + * + * <p>This initializer creates a new object and uses it as the initial + * value of the field. The constructor of the created object receives + * the parameters: + * + * <ul><code>Object obj</code> - the object including the field.<br> + * <code>Object[] args</code> - the parameters passed to the + * constructor of the object including the + * filed. + * </ul> + * + * <p>If the initialized field is static, then the constructor does + * not receive any parameters. + * + * @param objectType the class instantiated for the initial value. + * + * @see javassist.CtField.Initializer#byNewArray(CtClass,int) + * @see javassist.CtField.Initializer#byNewArray(CtClass,int[]) + */ + public static Initializer byNewWithParams(CtClass objectType) { + NewInitializer i = new NewInitializer(); + i.objectType = objectType; + i.stringParams = null; + i.withConstructorParams = true; + return i; + } + + /** + * Makes an initializer creating a new object. + * + * <p>This initializer creates a new object and uses it as the initial + * value of the field. The constructor of the created object receives + * the parameters: + * + * <ul><code>Object obj</code> - the object including the field.<br> + * <code>String[] strs</code> - the character strings specified + * by <code>stringParams</code><br> + * <code>Object[] args</code> - the parameters passed to the + * constructor of the object including the + * filed. + * </ul> + * + * <p>If the initialized field is static, then the constructor receives + * only <code>strs</code>. + * + * @param objectType the class instantiated for the initial value. + * @param stringParams the array of strings passed to the + * constructor. + */ + public static Initializer byNewWithParams(CtClass objectType, + String[] stringParams) { + NewInitializer i = new NewInitializer(); + i.objectType = objectType; + i.stringParams = stringParams; + i.withConstructorParams = true; + return i; + } + + /** + * Makes an initializer calling a static method. + * + * <p>This initializer calls a static method and uses the returned + * value as the initial value of the field. + * The called method receives the parameters: + * + * <ul><code>Object obj</code> - the object including the field.<br> + * </ul> + * + * <p>If the initialized field is static, then the method does + * not receive any parameters. + * + * <p>The type of the returned value must be the same as the field + * type. + * + * @param methodClass the class that the static method is + * declared in. + * @param methodName the name of the satic method. + */ + public static Initializer byCall(CtClass methodClass, + String methodName) { + MethodInitializer i = new MethodInitializer(); + i.objectType = methodClass; + i.methodName = methodName; + i.stringParams = null; + i.withConstructorParams = false; + return i; + } + + /** + * Makes an initializer calling a static method. + * + * <p>This initializer calls a static method and uses the returned + * value as the initial value of the field. The called method + * receives the parameters: + * + * <ul><code>Object obj</code> - the object including the field.<br> + * <code>String[] strs</code> - the character strings specified + * by <code>stringParams</code><br> + * </ul> + * + * <p>If the initialized field is static, then the method + * receive only <code>strs</code>. + * + * <p>The type of the returned value must be the same as the field + * type. + * + * @param methodClass the class that the static method is + * declared in. + * @param methodName the name of the satic method. + * @param stringParams the array of strings passed to the + * static method. + */ + public static Initializer byCall(CtClass methodClass, + String methodName, + String[] stringParams) { + MethodInitializer i = new MethodInitializer(); + i.objectType = methodClass; + i.methodName = methodName; + i.stringParams = stringParams; + i.withConstructorParams = false; + return i; + } + + /** + * Makes an initializer calling a static method. + * + * <p>This initializer calls a static method and uses the returned + * value as the initial value of the field. The called method + * receives the parameters: + * + * <ul><code>Object obj</code> - the object including the field.<br> + * <code>Object[] args</code> - the parameters passed to the + * constructor of the object including the + * filed. + * </ul> + * + * <p>If the initialized field is static, then the method does + * not receive any parameters. + * + * <p>The type of the returned value must be the same as the field + * type. + * + * @param methodClass the class that the static method is + * declared in. + * @param methodName the name of the satic method. + */ + public static Initializer byCallWithParams(CtClass methodClass, + String methodName) { + MethodInitializer i = new MethodInitializer(); + i.objectType = methodClass; + i.methodName = methodName; + i.stringParams = null; + i.withConstructorParams = true; + return i; + } + + /** + * Makes an initializer calling a static method. + * + * <p>This initializer calls a static method and uses the returned + * value as the initial value of the field. The called method + * receives the parameters: + * + * <ul><code>Object obj</code> - the object including the field.<br> + * <code>String[] strs</code> - the character strings specified + * by <code>stringParams</code><br> + * <code>Object[] args</code> - the parameters passed to the + * constructor of the object including the + * filed. + * </ul> + * + * <p>If the initialized field is static, then the method + * receive only <code>strs</code>. + * + * <p>The type of the returned value must be the same as the field + * type. + * + * @param methodClass the class that the static method is + * declared in. + * @param methodName the name of the satic method. + * @param stringParams the array of strings passed to the + * static method. + */ + public static Initializer byCallWithParams(CtClass methodClass, + String methodName, String[] stringParams) { + MethodInitializer i = new MethodInitializer(); + i.objectType = methodClass; + i.methodName = methodName; + i.stringParams = stringParams; + i.withConstructorParams = true; + return i; + } + + /** + * Makes an initializer creating a new array. + * + * @param type the type of the array. + * @param size the size of the array. + * @throws NotFoundException if the type of the array components + * is not found. + */ + public static Initializer byNewArray(CtClass type, int size) + throws NotFoundException + { + return new ArrayInitializer(type.getComponentType(), size); + } + + /** + * Makes an initializer creating a new multi-dimensional array. + * + * @param type the type of the array. + * @param sizes an <code>int</code> array of the size in every + * dimension. + * The first element is the size in the first + * dimension. The second is in the second, etc. + */ + public static Initializer byNewArray(CtClass type, int[] sizes) { + return new MultiArrayInitializer(type, sizes); + } + + /** + * Makes an initializer. + * + * @param source initializer expression. + */ + public static Initializer byExpr(String source) { + return new CodeInitializer(source); + } + + static Initializer byExpr(ASTree source) { + return new PtreeInitializer(source); + } + + // Check whether this initializer is valid for the field type. + // If it is invaild, this method throws an exception. + void check(String desc) throws CannotCompileException {} + + // produce codes for initialization + abstract int compile(CtClass type, String name, Bytecode code, + CtClass[] parameters, Javac drv) + throws CannotCompileException; + + // produce codes for initialization + abstract int compileIfStatic(CtClass type, String name, + Bytecode code, Javac drv) throws CannotCompileException; + + // returns the index of CONSTANT_Integer_info etc + // if the value is constant. Otherwise, 0. + int getConstantValue(ConstPool cp, CtClass type) { return 0; } + } + + static abstract class CodeInitializer0 extends Initializer { + abstract void compileExpr(Javac drv) throws CompileError; + + int compile(CtClass type, String name, Bytecode code, + CtClass[] parameters, Javac drv) + throws CannotCompileException + { + try { + code.addAload(0); + compileExpr(drv); + code.addPutfield(Bytecode.THIS, name, Descriptor.of(type)); + return code.getMaxStack(); + } + catch (CompileError e) { + throw new CannotCompileException(e); + } + } + + int compileIfStatic(CtClass type, String name, Bytecode code, + Javac drv) throws CannotCompileException + { + try { + compileExpr(drv); + code.addPutstatic(Bytecode.THIS, name, Descriptor.of(type)); + return code.getMaxStack(); + } + catch (CompileError e) { + throw new CannotCompileException(e); + } + } + + int getConstantValue2(ConstPool cp, CtClass type, ASTree tree) { + if (type.isPrimitive()) { + if (tree instanceof IntConst) { + long value = ((IntConst)tree).get(); + if (type == CtClass.doubleType) + return cp.addDoubleInfo((double)value); + else if (type == CtClass.floatType) + return cp.addFloatInfo((float)value); + else if (type == CtClass.longType) + return cp.addLongInfo(value); + else if (type != CtClass.voidType) + return cp.addIntegerInfo((int)value); + } + else if (tree instanceof DoubleConst) { + double value = ((DoubleConst)tree).get(); + if (type == CtClass.floatType) + return cp.addFloatInfo((float)value); + else if (type == CtClass.doubleType) + return cp.addDoubleInfo(value); + } + } + else if (tree instanceof StringL + && type.getName().equals(javaLangString)) + return cp.addStringInfo(((StringL)tree).get()); + + return 0; + } + } + + static class CodeInitializer extends CodeInitializer0 { + private String expression; + + CodeInitializer(String expr) { expression = expr; } + + void compileExpr(Javac drv) throws CompileError { + drv.compileExpr(expression); + } + + int getConstantValue(ConstPool cp, CtClass type) { + try { + ASTree t = Javac.parseExpr(expression, new SymbolTable()); + return getConstantValue2(cp, type, t); + } + catch (CompileError e) { + return 0; + } + } + } + + static class PtreeInitializer extends CodeInitializer0 { + private ASTree expression; + + PtreeInitializer(ASTree expr) { expression = expr; } + + void compileExpr(Javac drv) throws CompileError { + drv.compileExpr(expression); + } + + int getConstantValue(ConstPool cp, CtClass type) { + return getConstantValue2(cp, type, expression); + } + } + + /** + * A field initialized with a parameter passed to the constructor + * of the class containing that field. + */ + static class ParamInitializer extends Initializer { + int nthParam; + + ParamInitializer() {} + + int compile(CtClass type, String name, Bytecode code, + CtClass[] parameters, Javac drv) + throws CannotCompileException + { + if (parameters != null && nthParam < parameters.length) { + code.addAload(0); + int nth = nthParamToLocal(nthParam, parameters, false); + int s = code.addLoad(nth, type) + 1; + code.addPutfield(Bytecode.THIS, name, Descriptor.of(type)); + return s; // stack size + } + else + return 0; // do not initialize + } + + /** + * Computes the index of the local variable that the n-th parameter + * is assigned to. + * + * @param nth n-th parameter + * @param params list of parameter types + * @param isStatic true if the method is static. + */ + static int nthParamToLocal(int nth, CtClass[] params, + boolean isStatic) { + CtClass longType = CtClass.longType; + CtClass doubleType = CtClass.doubleType; + int k; + if (isStatic) + k = 0; + else + k = 1; // 0 is THIS. + + for (int i = 0; i < nth; ++i) { + CtClass type = params[i]; + if (type == longType || type == doubleType) + k += 2; + else + ++k; + } + + return k; + } + + int compileIfStatic(CtClass type, String name, Bytecode code, + Javac drv) throws CannotCompileException + { + return 0; + } + } + + /** + * A field initialized with an object created by the new operator. + */ + static class NewInitializer extends Initializer { + CtClass objectType; + String[] stringParams; + boolean withConstructorParams; + + NewInitializer() {} + + /** + * Produces codes in which a new object is created and assigned to + * the field as the initial value. + */ + int compile(CtClass type, String name, Bytecode code, + CtClass[] parameters, Javac drv) + throws CannotCompileException + { + int stacksize; + + code.addAload(0); + code.addNew(objectType); + code.add(Bytecode.DUP); + code.addAload(0); + + if (stringParams == null) + stacksize = 4; + else + stacksize = compileStringParameter(code) + 4; + + if (withConstructorParams) + stacksize += CtNewWrappedMethod.compileParameterList(code, + parameters, 1); + + code.addInvokespecial(objectType, "<init>", getDescriptor()); + code.addPutfield(Bytecode.THIS, name, Descriptor.of(type)); + return stacksize; + } + + private String getDescriptor() { + final String desc3 + = "(Ljava/lang/Object;[Ljava/lang/String;[Ljava/lang/Object;)V"; + + if (stringParams == null) + if (withConstructorParams) + return "(Ljava/lang/Object;[Ljava/lang/Object;)V"; + else + return "(Ljava/lang/Object;)V"; + else + if (withConstructorParams) + return desc3; + else + return "(Ljava/lang/Object;[Ljava/lang/String;)V"; + } + + /** + * Produces codes for a static field. + */ + int compileIfStatic(CtClass type, String name, Bytecode code, + Javac drv) throws CannotCompileException + { + String desc; + + code.addNew(objectType); + code.add(Bytecode.DUP); + + int stacksize = 2; + if (stringParams == null) + desc = "()V"; + else { + desc = "([Ljava/lang/String;)V"; + stacksize += compileStringParameter(code); + } + + code.addInvokespecial(objectType, "<init>", desc); + code.addPutstatic(Bytecode.THIS, name, Descriptor.of(type)); + return stacksize; + } + + protected final int compileStringParameter(Bytecode code) + throws CannotCompileException + { + int nparam = stringParams.length; + code.addIconst(nparam); + code.addAnewarray(javaLangString); + for (int j = 0; j < nparam; ++j) { + code.add(Bytecode.DUP); // dup + code.addIconst(j); // iconst_<j> + code.addLdc(stringParams[j]); // ldc ... + code.add(Bytecode.AASTORE); // aastore + } + + return 4; + } + + } + + /** + * A field initialized with the result of a static method call. + */ + static class MethodInitializer extends NewInitializer { + String methodName; + // the method class is specified by objectType. + + MethodInitializer() {} + + /** + * Produces codes in which a new object is created and assigned to + * the field as the initial value. + */ + int compile(CtClass type, String name, Bytecode code, + CtClass[] parameters, Javac drv) + throws CannotCompileException + { + int stacksize; + + code.addAload(0); + code.addAload(0); + + if (stringParams == null) + stacksize = 2; + else + stacksize = compileStringParameter(code) + 2; + + if (withConstructorParams) + stacksize += CtNewWrappedMethod.compileParameterList(code, + parameters, 1); + + String typeDesc = Descriptor.of(type); + String mDesc = getDescriptor() + typeDesc; + code.addInvokestatic(objectType, methodName, mDesc); + code.addPutfield(Bytecode.THIS, name, typeDesc); + return stacksize; + } + + private String getDescriptor() { + final String desc3 + = "(Ljava/lang/Object;[Ljava/lang/String;[Ljava/lang/Object;)"; + + if (stringParams == null) + if (withConstructorParams) + return "(Ljava/lang/Object;[Ljava/lang/Object;)"; + else + return "(Ljava/lang/Object;)"; + else + if (withConstructorParams) + return desc3; + else + return "(Ljava/lang/Object;[Ljava/lang/String;)"; + } + + /** + * Produces codes for a static field. + */ + int compileIfStatic(CtClass type, String name, Bytecode code, + Javac drv) throws CannotCompileException + { + String desc; + + int stacksize = 1; + if (stringParams == null) + desc = "()"; + else { + desc = "([Ljava/lang/String;)"; + stacksize += compileStringParameter(code); + } + + String typeDesc = Descriptor.of(type); + code.addInvokestatic(objectType, methodName, desc + typeDesc); + code.addPutstatic(Bytecode.THIS, name, typeDesc); + return stacksize; + } + } + + static class IntInitializer extends Initializer { + int value; + + IntInitializer(int v) { value = v; } + + void check(String desc) throws CannotCompileException { + char c = desc.charAt(0); + if (c != 'I' && c != 'S' && c != 'B' && c != 'C' && c != 'Z') + throw new CannotCompileException("type mismatch"); + } + + int compile(CtClass type, String name, Bytecode code, + CtClass[] parameters, Javac drv) + throws CannotCompileException + { + code.addAload(0); + code.addIconst(value); + code.addPutfield(Bytecode.THIS, name, Descriptor.of(type)); + return 2; // stack size + } + + int compileIfStatic(CtClass type, String name, Bytecode code, + Javac drv) throws CannotCompileException + { + code.addIconst(value); + code.addPutstatic(Bytecode.THIS, name, Descriptor.of(type)); + return 1; // stack size + } + + int getConstantValue(ConstPool cp, CtClass type) { + return cp.addIntegerInfo(value); + } + } + + static class LongInitializer extends Initializer { + long value; + + LongInitializer(long v) { value = v; } + + void check(String desc) throws CannotCompileException { + if (!desc.equals("J")) + throw new CannotCompileException("type mismatch"); + } + + int compile(CtClass type, String name, Bytecode code, + CtClass[] parameters, Javac drv) + throws CannotCompileException + { + code.addAload(0); + code.addLdc2w(value); + code.addPutfield(Bytecode.THIS, name, Descriptor.of(type)); + return 3; // stack size + } + + int compileIfStatic(CtClass type, String name, Bytecode code, + Javac drv) throws CannotCompileException + { + code.addLdc2w(value); + code.addPutstatic(Bytecode.THIS, name, Descriptor.of(type)); + return 2; // stack size + } + + int getConstantValue(ConstPool cp, CtClass type) { + if (type == CtClass.longType) + return cp.addLongInfo(value); + else + return 0; + } + } + + static class FloatInitializer extends Initializer { + float value; + + FloatInitializer(float v) { value = v; } + + void check(String desc) throws CannotCompileException { + if (!desc.equals("F")) + throw new CannotCompileException("type mismatch"); + } + + int compile(CtClass type, String name, Bytecode code, + CtClass[] parameters, Javac drv) + throws CannotCompileException + { + code.addAload(0); + code.addFconst(value); + code.addPutfield(Bytecode.THIS, name, Descriptor.of(type)); + return 3; // stack size + } + + int compileIfStatic(CtClass type, String name, Bytecode code, + Javac drv) throws CannotCompileException + { + code.addFconst(value); + code.addPutstatic(Bytecode.THIS, name, Descriptor.of(type)); + return 2; // stack size + } + + int getConstantValue(ConstPool cp, CtClass type) { + if (type == CtClass.floatType) + return cp.addFloatInfo(value); + else + return 0; + } + } + + static class DoubleInitializer extends Initializer { + double value; + + DoubleInitializer(double v) { value = v; } + + void check(String desc) throws CannotCompileException { + if (!desc.equals("D")) + throw new CannotCompileException("type mismatch"); + } + + int compile(CtClass type, String name, Bytecode code, + CtClass[] parameters, Javac drv) + throws CannotCompileException + { + code.addAload(0); + code.addLdc2w(value); + code.addPutfield(Bytecode.THIS, name, Descriptor.of(type)); + return 3; // stack size + } + + int compileIfStatic(CtClass type, String name, Bytecode code, + Javac drv) throws CannotCompileException + { + code.addLdc2w(value); + code.addPutstatic(Bytecode.THIS, name, Descriptor.of(type)); + return 2; // stack size + } + + int getConstantValue(ConstPool cp, CtClass type) { + if (type == CtClass.doubleType) + return cp.addDoubleInfo(value); + else + return 0; + } + } + + static class StringInitializer extends Initializer { + String value; + + StringInitializer(String v) { value = v; } + + int compile(CtClass type, String name, Bytecode code, + CtClass[] parameters, Javac drv) + throws CannotCompileException + { + code.addAload(0); + code.addLdc(value); + code.addPutfield(Bytecode.THIS, name, Descriptor.of(type)); + return 2; // stack size + } + + int compileIfStatic(CtClass type, String name, Bytecode code, + Javac drv) throws CannotCompileException + { + code.addLdc(value); + code.addPutstatic(Bytecode.THIS, name, Descriptor.of(type)); + return 1; // stack size + } + + int getConstantValue(ConstPool cp, CtClass type) { + if (type.getName().equals(javaLangString)) + return cp.addStringInfo(value); + else + return 0; + } + } + + static class ArrayInitializer extends Initializer { + CtClass type; + int size; + + ArrayInitializer(CtClass t, int s) { type = t; size = s; } + + private void addNewarray(Bytecode code) { + if (type.isPrimitive()) + code.addNewarray(((CtPrimitiveType)type).getArrayType(), + size); + else + code.addAnewarray(type, size); + } + + int compile(CtClass type, String name, Bytecode code, + CtClass[] parameters, Javac drv) + throws CannotCompileException + { + code.addAload(0); + addNewarray(code); + code.addPutfield(Bytecode.THIS, name, Descriptor.of(type)); + return 2; // stack size + } + + int compileIfStatic(CtClass type, String name, Bytecode code, + Javac drv) throws CannotCompileException + { + addNewarray(code); + code.addPutstatic(Bytecode.THIS, name, Descriptor.of(type)); + return 1; // stack size + } + } + + static class MultiArrayInitializer extends Initializer { + CtClass type; + int[] dim; + + MultiArrayInitializer(CtClass t, int[] d) { type = t; dim = d; } + + void check(String desc) throws CannotCompileException { + if (desc.charAt(0) != '[') + throw new CannotCompileException("type mismatch"); + } + + int compile(CtClass type, String name, Bytecode code, + CtClass[] parameters, Javac drv) + throws CannotCompileException + { + code.addAload(0); + int s = code.addMultiNewarray(type, dim); + code.addPutfield(Bytecode.THIS, name, Descriptor.of(type)); + return s + 1; // stack size + } + + int compileIfStatic(CtClass type, String name, Bytecode code, + Javac drv) throws CannotCompileException + { + int s = code.addMultiNewarray(type, dim); + code.addPutstatic(Bytecode.THIS, name, Descriptor.of(type)); + return s; // stack size + } + } +} diff --git a/src/main/javassist/CtMember.java b/src/main/javassist/CtMember.java new file mode 100644 index 0000000..0956642 --- /dev/null +++ b/src/main/javassist/CtMember.java @@ -0,0 +1,295 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist; + +/** + * An instance of <code>CtMember</code> represents a field, a constructor, + * or a method. + */ +public abstract class CtMember { + CtMember next; // for internal use + protected CtClass declaringClass; + + /* Make a circular link of CtMembers declared in the + * same class so that they are garbage-collected together + * at the same time. + */ + static class Cache extends CtMember { + protected void extendToString(StringBuffer buffer) {} + public boolean hasAnnotation(Class clz) { return false; } + public Object getAnnotation(Class clz) + throws ClassNotFoundException { return null; } + public Object[] getAnnotations() + throws ClassNotFoundException { return null; } + public byte[] getAttribute(String name) { return null; } + public Object[] getAvailableAnnotations() { return null; } + public int getModifiers() { return 0; } + public String getName() { return null; } + public String getSignature() { return null; } + public void setAttribute(String name, byte[] data) {} + public void setModifiers(int mod) {} + + private CtMember methodTail; + private CtMember consTail; // constructor tail + private CtMember fieldTail; + + Cache(CtClassType decl) { + super(decl); + methodTail = this; + consTail = this; + fieldTail = this; + fieldTail.next = this; + } + + CtMember methodHead() { return this; } + CtMember lastMethod() { return methodTail; } + CtMember consHead() { return methodTail; } // may include a static initializer + CtMember lastCons() { return consTail; } + CtMember fieldHead() { return consTail; } + CtMember lastField() { return fieldTail; } + + void addMethod(CtMember method) { + method.next = methodTail.next; + methodTail.next = method; + if (methodTail == consTail) { + consTail = method; + if (methodTail == fieldTail) + fieldTail = method; + } + + methodTail = method; + } + + /* Both constructors and a class initializer. + */ + void addConstructor(CtMember cons) { + cons.next = consTail.next; + consTail.next = cons; + if (consTail == fieldTail) + fieldTail = cons; + + consTail = cons; + } + + void addField(CtMember field) { + field.next = this; // or fieldTail.next + fieldTail.next = field; + fieldTail = field; + } + + static int count(CtMember head, CtMember tail) { + int n = 0; + while (head != tail) { + n++; + head = head.next; + } + + return n; + } + + void remove(CtMember mem) { + CtMember m = this; + CtMember node; + while ((node = m.next) != this) { + if (node == mem) { + m.next = node.next; + if (node == methodTail) + methodTail = m; + + if (node == consTail) + consTail = m; + + if (node == fieldTail) + fieldTail = m; + + break; + } + else + m = m.next; + } + } + } + + protected CtMember(CtClass clazz) { + declaringClass = clazz; + next = null; + } + + final CtMember next() { return next; } + + /** + * This method is invoked when setName() or replaceClassName() + * in CtClass is called. + * + * @see CtMethod#nameReplaced() + */ + void nameReplaced() {} + + public String toString() { + StringBuffer buffer = new StringBuffer(getClass().getName()); + buffer.append("@"); + buffer.append(Integer.toHexString(hashCode())); + buffer.append("["); + buffer.append(Modifier.toString(getModifiers())); + extendToString(buffer); + buffer.append("]"); + return buffer.toString(); + } + + /** + * Invoked by {@link #toString()} to add to the buffer and provide the + * complete value. Subclasses should invoke this method, adding a + * space before each token. The modifiers for the member are + * provided first; subclasses should provide additional data such + * as return type, field or method name, etc. + */ + protected abstract void extendToString(StringBuffer buffer); + + /** + * Returns the class that declares this member. + */ + public CtClass getDeclaringClass() { return declaringClass; } + + /** + * Returns true if this member is accessible from the given class. + */ + public boolean visibleFrom(CtClass clazz) { + int mod = getModifiers(); + if (Modifier.isPublic(mod)) + return true; + else if (Modifier.isPrivate(mod)) + return clazz == declaringClass; + else { // package or protected + String declName = declaringClass.getPackageName(); + String fromName = clazz.getPackageName(); + boolean visible; + if (declName == null) + visible = fromName == null; + else + visible = declName.equals(fromName); + + if (!visible && Modifier.isProtected(mod)) + return clazz.subclassOf(declaringClass); + + return visible; + } + } + + /** + * Obtains the modifiers of the member. + * + * @return modifiers encoded with + * <code>javassist.Modifier</code>. + * @see Modifier + */ + public abstract int getModifiers(); + + /** + * Sets the encoded modifiers of the member. + * + * @see Modifier + */ + public abstract void setModifiers(int mod); + + /** + * Returns true if the class has the specified annotation class. + * + * @param clz the annotation class. + * @return <code>true</code> if the annotation is found, otherwise <code>false</code>. + * @since 3.11 + */ + public abstract boolean hasAnnotation(Class clz); + + /** + * Returns the annotation if the class has the specified annotation class. + * For example, if an annotation <code>@Author</code> is associated + * with this member, an <code>Author</code> object is returned. + * The member values can be obtained by calling methods on + * the <code>Author</code> object. + * + * @param clz the annotation class. + * @return the annotation if found, otherwise <code>null</code>. + * @since 3.11 + */ + public abstract Object getAnnotation(Class clz) throws ClassNotFoundException; + + /** + * Returns the annotations associated with this member. + * For example, if an annotation <code>@Author</code> is associated + * with this member, the returned array contains an <code>Author</code> + * object. The member values can be obtained by calling methods on + * the <code>Author</code> object. + * + * @return an array of annotation-type objects. + * @see CtClass#getAnnotations() + */ + public abstract Object[] getAnnotations() throws ClassNotFoundException; + + /** + * Returns the annotations associated with this member. + * This method is equivalent to <code>getAnnotations()</code> + * except that, if any annotations are not on the classpath, + * they are not included in the returned array. + * + * @return an array of annotation-type objects. + * @see #getAnnotations() + * @see CtClass#getAvailableAnnotations() + * @since 3.3 + */ + public abstract Object[] getAvailableAnnotations(); + + /** + * Obtains the name of the member. + * + * <p>As for constructor names, see <code>getName()</code> + * in <code>CtConstructor</code>. + * + * @see CtConstructor#getName() + */ + public abstract String getName(); + + /** + * Returns the character string representing the signature of the member. + * If two members have the same signature (parameter types etc.), + * <code>getSignature()</code> returns the same string. + */ + public abstract String getSignature(); + + /** + * Obtains a user-defined attribute with the given name. + * If that attribute is not found in the class file, this + * method returns null. + * + * <p>Note that an attribute is a data block specified by + * the class file format. + * See {@link javassist.bytecode.AttributeInfo}. + * + * @param name attribute name + */ + public abstract byte[] getAttribute(String name); + + /** + * Adds a user-defined attribute. The attribute is saved in the class file. + * + * <p>Note that an attribute is a data block specified by + * the class file format. + * See {@link javassist.bytecode.AttributeInfo}. + * + * @param name attribute name + * @param data attribute value + */ + public abstract void setAttribute(String name, byte[] data); +} diff --git a/src/main/javassist/CtMethod.java b/src/main/javassist/CtMethod.java new file mode 100644 index 0000000..727ff5b --- /dev/null +++ b/src/main/javassist/CtMethod.java @@ -0,0 +1,435 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist; + +import javassist.bytecode.*; + +/** + * An instance of <code>CtMethod</code> represents a method. + * + * <p>See the super class <code>CtBehavior</code> since + * a number of useful methods are in <code>CtBehavior</code>. + * A number of useful factory methods are in <code>CtNewMethod</code>. + * + * @see CtClass#getDeclaredMethods() + * @see CtNewMethod + */ +public final class CtMethod extends CtBehavior { + protected String cachedStringRep; + + /** + * @see #make(MethodInfo minfo, CtClass declaring) + */ + CtMethod(MethodInfo minfo, CtClass declaring) { + super(declaring, minfo); + cachedStringRep = null; + } + + /** + * Creates a public abstract method. The created method must be + * added to a class with <code>CtClass.addMethod()</code>. + * + * @param declaring the class to which the created method is added. + * @param returnType the type of the returned value + * @param mname the method name + * @param parameters a list of the parameter types + * + * @see CtClass#addMethod(CtMethod) + */ + public CtMethod(CtClass returnType, String mname, + CtClass[] parameters, CtClass declaring) { + this(null, declaring); + ConstPool cp = declaring.getClassFile2().getConstPool(); + String desc = Descriptor.ofMethod(returnType, parameters); + methodInfo = new MethodInfo(cp, mname, desc); + setModifiers(Modifier.PUBLIC | Modifier.ABSTRACT); + } + + /** + * Creates a copy of a <code>CtMethod</code> object. + * The created method must be + * added to a class with <code>CtClass.addMethod()</code>. + * + * <p>All occurrences of class names in the created method + * are replaced with names specified by + * <code>map</code> if <code>map</code> is not <code>null</code>. + * + * <p>For example, suppose that a method <code>at()</code> is as + * follows: + * + * <ul><pre>public X at(int i) { + * return (X)super.elementAt(i); + * }</pre></ul> + * + * <p>(<code>X</code> is a class name.) If <code>map</code> substitutes + * <code>String</code> for <code>X</code>, then the created method is: + * + * <ul><pre>public String at(int i) { + * return (String)super.elementAt(i); + * }</pre></ul> + * + * <p>By default, all the occurrences of the names of the class + * declaring <code>at()</code> and the superclass are replaced + * with the name of the class and the superclass that the + * created method is added to. + * This is done whichever <code>map</code> is null or not. + * To prevent this replacement, call <code>ClassMap.fix()</code> + * or <code>put()</code> to explicitly specify replacement. + * + * <p><b>Note:</b> if the <code>.class</code> notation (for example, + * <code>String.class</code>) is included in an expression, the + * Javac compiler may produce a helper method. + * Since this constructor never + * copies this helper method, the programmers have the responsiblity of + * copying it. Otherwise, use <code>Class.forName()</code> in the + * expression. + * + * @param src the source method. + * @param declaring the class to which the created method is added. + * @param map the hashtable associating original class names + * with substituted names. + * It can be <code>null</code>. + * + * @see CtClass#addMethod(CtMethod) + * @see ClassMap#fix(String) + */ + public CtMethod(CtMethod src, CtClass declaring, ClassMap map) + throws CannotCompileException + { + this(null, declaring); + copy(src, false, map); + } + + /** + * Compiles the given source code and creates a method. + * This method simply delegates to <code>make()</code> in + * <code>CtNewMethod</code>. See it for more details. + * <code>CtNewMethod</code> has a number of useful factory methods. + * + * @param src the source text. + * @param declaring the class to which the created method is added. + * @see CtNewMethod#make(String, CtClass) + */ + public static CtMethod make(String src, CtClass declaring) + throws CannotCompileException + { + return CtNewMethod.make(src, declaring); + } + + /** + * Creates a method from a <code>MethodInfo</code> object. + * + * @param declaring the class declaring the method. + * @throws CannotCompileException if the the <code>MethodInfo</code> + * object and the declaring class have different + * <code>ConstPool</code> objects + * @since 3.6 + */ + public static CtMethod make(MethodInfo minfo, CtClass declaring) + throws CannotCompileException + { + if (declaring.getClassFile2().getConstPool() != minfo.getConstPool()) + throw new CannotCompileException("bad declaring class"); + + return new CtMethod(minfo, declaring); + } + + /** + * Returns a hash code value for the method. + * If two methods have the same name and signature, then + * the hash codes for the two methods are equal. + */ + public int hashCode() { + return getStringRep().hashCode(); + } + + /** + * This method is invoked when setName() or replaceClassName() + * in CtClass is called. + */ + void nameReplaced() { + cachedStringRep = null; + } + + /* This method is also called by CtClassType.getMethods0(). + */ + final String getStringRep() { + if (cachedStringRep == null) + cachedStringRep = methodInfo.getName() + + Descriptor.getParamDescriptor(methodInfo.getDescriptor()); + + return cachedStringRep; + } + + /** + * Indicates whether <code>obj</code> has the same name and the + * same signature as this method. + */ + public boolean equals(Object obj) { + return obj != null && obj instanceof CtMethod + && ((CtMethod)obj).getStringRep().equals(getStringRep()); + } + + /** + * Returns the method name followed by parameter types + * such as <code>javassist.CtMethod.setBody(String)</code>. + * + * @since 3.5 + */ + public String getLongName() { + return getDeclaringClass().getName() + "." + + getName() + Descriptor.toString(getSignature()); + } + + /** + * Obtains the name of this method. + */ + public String getName() { + return methodInfo.getName(); + } + + /** + * Changes the name of this method. + */ + public void setName(String newname) { + declaringClass.checkModify(); + methodInfo.setName(newname); + } + + /** + * Obtains the type of the returned value. + */ + public CtClass getReturnType() throws NotFoundException { + return getReturnType0(); + } + + /** + * Returns true if the method body is empty, that is, <code>{}</code>. + * It also returns true if the method is an abstract method. + */ + public boolean isEmpty() { + CodeAttribute ca = getMethodInfo2().getCodeAttribute(); + if (ca == null) // abstract or native + return (getModifiers() & Modifier.ABSTRACT) != 0; + + CodeIterator it = ca.iterator(); + try { + return it.hasNext() && it.byteAt(it.next()) == Opcode.RETURN + && !it.hasNext(); + } + catch (BadBytecode e) {} + return false; + } + + /** + * Copies a method body from another method. + * If this method is abstract, the abstract modifier is removed + * after the method body is copied. + * + * <p>All occurrences of the class names in the copied method body + * are replaced with the names specified by + * <code>map</code> if <code>map</code> is not <code>null</code>. + * + * @param src the method that the body is copied from. + * @param map the hashtable associating original class names + * with substituted names. + * It can be <code>null</code>. + */ + public void setBody(CtMethod src, ClassMap map) + throws CannotCompileException + { + setBody0(src.declaringClass, src.methodInfo, + declaringClass, methodInfo, map); + } + + /** + * Replace a method body with a new method body wrapping the + * given method. + * + * @param mbody the wrapped method + * @param constParam the constant parameter given to + * the wrapped method + * (maybe <code>null</code>). + * + * @see CtNewMethod#wrapped(CtClass,String,CtClass[],CtClass[],CtMethod,CtMethod.ConstParameter,CtClass) + */ + public void setWrappedBody(CtMethod mbody, ConstParameter constParam) + throws CannotCompileException + { + declaringClass.checkModify(); + + CtClass clazz = getDeclaringClass(); + CtClass[] params; + CtClass retType; + try { + params = getParameterTypes(); + retType = getReturnType(); + } + catch (NotFoundException e) { + throw new CannotCompileException(e); + } + + Bytecode code = CtNewWrappedMethod.makeBody(clazz, + clazz.getClassFile2(), + mbody, + params, retType, + constParam); + CodeAttribute cattr = code.toCodeAttribute(); + methodInfo.setCodeAttribute(cattr); + methodInfo.setAccessFlags(methodInfo.getAccessFlags() + & ~AccessFlag.ABSTRACT); + // rebuilding a stack map table is not needed. + } + + // inner classes + + /** + * Instances of this class represent a constant parameter. + * They are used to specify the parameter given to the methods + * created by <code>CtNewMethod.wrapped()</code>. + * + * @see CtMethod#setWrappedBody(CtMethod,CtMethod.ConstParameter) + * @see CtNewMethod#wrapped(CtClass,String,CtClass[],CtClass[],CtMethod,CtMethod.ConstParameter,CtClass) + * @see CtNewConstructor#make(CtClass[],CtClass[],int,CtMethod,CtMethod.ConstParameter,CtClass) + */ + public static class ConstParameter { + /** + * Makes an integer constant. + * + * @param i the constant value. + */ + public static ConstParameter integer(int i) { + return new IntConstParameter(i); + } + + /** + * Makes a long integer constant. + * + * @param i the constant value. + */ + public static ConstParameter integer(long i) { + return new LongConstParameter(i); + } + + /** + * Makes an <code>String</code> constant. + * + * @param s the constant value. + */ + public static ConstParameter string(String s) { + return new StringConstParameter(s); + } + + ConstParameter() {} + + /** + * @return the size of the stack consumption. + */ + int compile(Bytecode code) throws CannotCompileException { + return 0; + } + + String descriptor() { + return defaultDescriptor(); + } + + /** + * @see CtNewWrappedMethod + */ + static String defaultDescriptor() { + return "([Ljava/lang/Object;)Ljava/lang/Object;"; + } + + /** + * Returns the descriptor for constructors. + * + * @see CtNewWrappedConstructor + */ + String constDescriptor() { + return defaultConstDescriptor(); + } + + /** + * Returns the default descriptor for constructors. + */ + static String defaultConstDescriptor() { + return "([Ljava/lang/Object;)V"; + } + } + + static class IntConstParameter extends ConstParameter { + int param; + + IntConstParameter(int i) { + param = i; + } + + int compile(Bytecode code) throws CannotCompileException { + code.addIconst(param); + return 1; + } + + String descriptor() { + return "([Ljava/lang/Object;I)Ljava/lang/Object;"; + } + + String constDescriptor() { + return "([Ljava/lang/Object;I)V"; + } + } + + static class LongConstParameter extends ConstParameter { + long param; + + LongConstParameter(long l) { + param = l; + } + + int compile(Bytecode code) throws CannotCompileException { + code.addLconst(param); + return 2; + } + + String descriptor() { + return "([Ljava/lang/Object;J)Ljava/lang/Object;"; + } + + String constDescriptor() { + return "([Ljava/lang/Object;J)V"; + } + } + + static class StringConstParameter extends ConstParameter { + String param; + + StringConstParameter(String s) { + param = s; + } + + int compile(Bytecode code) throws CannotCompileException { + code.addLdc(param); + return 1; + } + + String descriptor() { + return "([Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/Object;"; + } + + String constDescriptor() { + return "([Ljava/lang/Object;Ljava/lang/String;)V"; + } + } +} diff --git a/src/main/javassist/CtNewClass.java b/src/main/javassist/CtNewClass.java new file mode 100644 index 0000000..ba48e93 --- /dev/null +++ b/src/main/javassist/CtNewClass.java @@ -0,0 +1,125 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist; + +import java.io.DataOutputStream; +import java.io.IOException; +import javassist.bytecode.ClassFile; + +class CtNewClass extends CtClassType { + /* true if the class is an interface. + */ + protected boolean hasConstructor; + + CtNewClass(String name, ClassPool cp, + boolean isInterface, CtClass superclass) { + super(name, cp); + wasChanged = true; + String superName; + if (isInterface || superclass == null) + superName = null; + else + superName = superclass.getName(); + + classfile = new ClassFile(isInterface, name, superName); + if (isInterface && superclass != null) + classfile.setInterfaces(new String[] { superclass.getName() }); + + setModifiers(Modifier.setPublic(getModifiers())); + hasConstructor = isInterface; + } + + protected void extendToString(StringBuffer buffer) { + if (hasConstructor) + buffer.append("hasConstructor "); + + super.extendToString(buffer); + } + + public void addConstructor(CtConstructor c) + throws CannotCompileException + { + hasConstructor = true; + super.addConstructor(c); + } + + public void toBytecode(DataOutputStream out) + throws CannotCompileException, IOException + { + if (!hasConstructor) + try { + inheritAllConstructors(); + hasConstructor = true; + } + catch (NotFoundException e) { + throw new CannotCompileException(e); + } + + super.toBytecode(out); + } + + /** + * Adds constructors inhrited from the super class. + * + * <p>After this method is called, the class inherits all the + * constructors from the super class. The added constructor + * calls the super's constructor with the same signature. + */ + public void inheritAllConstructors() + throws CannotCompileException, NotFoundException + { + CtClass superclazz; + CtConstructor[] cs; + + superclazz = getSuperclass(); + cs = superclazz.getDeclaredConstructors(); + + int n = 0; + for (int i = 0; i < cs.length; ++i) { + CtConstructor c = cs[i]; + int mod = c.getModifiers(); + if (isInheritable(mod, superclazz)) { + CtConstructor cons + = CtNewConstructor.make(c.getParameterTypes(), + c.getExceptionTypes(), this); + cons.setModifiers(mod & (Modifier.PUBLIC | Modifier.PROTECTED | Modifier.PRIVATE)); + addConstructor(cons); + ++n; + } + } + + if (n < 1) + throw new CannotCompileException( + "no inheritable constructor in " + superclazz.getName()); + + } + + private boolean isInheritable(int mod, CtClass superclazz) { + if (Modifier.isPrivate(mod)) + return false; + + if (Modifier.isPackage(mod)) { + String pname = getPackageName(); + String pname2 = superclazz.getPackageName(); + if (pname == null) + return pname2 == null; + else + return pname.equals(pname2); + } + + return true; + } +} diff --git a/src/main/javassist/CtNewConstructor.java b/src/main/javassist/CtNewConstructor.java new file mode 100644 index 0000000..f510356 --- /dev/null +++ b/src/main/javassist/CtNewConstructor.java @@ -0,0 +1,316 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist; + +import javassist.bytecode.*; +import javassist.compiler.Javac; +import javassist.compiler.CompileError; +import javassist.CtMethod.ConstParameter; + +/** + * A collection of static methods for creating a <code>CtConstructor</code>. + * An instance of this class does not make any sense. + * + * <p>A class initializer (static constructor) cannot be created by the + * methods in this class. Call <code>makeClassInitializer()</code> in + * <code>CtClass</code> and append code snippet to the body of the class + * initializer obtained by <code>makeClassInitializer()</code>. + * + * @see CtClass#addConstructor(CtConstructor) + * @see CtClass#makeClassInitializer() + */ +public class CtNewConstructor { + /** + * Specifies that no parameters are passed to a super-class' + * constructor. That is, the default constructor is invoked. + */ + public static final int PASS_NONE = 0; // call super() + + /** + * Specifies that parameters are converted into an array of + * <code>Object</code> and passed to a super-class' + * constructor. + */ + public static final int PASS_ARRAY = 1; // an array of parameters + + /** + * Specifies that parameters are passed <i>as is</i> + * to a super-class' constructor. The signature of that + * constructor must be the same as that of the created constructor. + */ + public static final int PASS_PARAMS = 2; + + /** + * Compiles the given source code and creates a constructor. + * The source code must include not only the constructor body + * but the whole declaration. + * + * @param src the source text. + * @param declaring the class to which the created constructor is added. + */ + public static CtConstructor make(String src, CtClass declaring) + throws CannotCompileException + { + Javac compiler = new Javac(declaring); + try { + CtMember obj = compiler.compile(src); + if (obj instanceof CtConstructor) { + // a stack map table has been already created. + return (CtConstructor)obj; + } + } + catch (CompileError e) { + throw new CannotCompileException(e); + } + + throw new CannotCompileException("not a constructor"); + } + + /** + * Creates a public constructor. + * + * @param parameters a list of the parameter types. + * @param exceptions a list of the exception types. + * @param body the source text of the constructor body. + * It must be a block surrounded by <code>{}</code>. + * If it is <code>null</code>, the substituted + * constructor body does nothing except calling + * <code>super()</code>. + * @param declaring the class to which the created method is added. + */ + public static CtConstructor make(CtClass[] parameters, + CtClass[] exceptions, + String body, CtClass declaring) + throws CannotCompileException + { + try { + CtConstructor cc = new CtConstructor(parameters, declaring); + cc.setExceptionTypes(exceptions); + cc.setBody(body); + return cc; + } + catch (NotFoundException e) { + throw new CannotCompileException(e); + } + } + + /** + * Creates a copy of a constructor. + * This is a convenience method for calling + * {@link CtConstructor#CtConstructor(CtConstructor, CtClass, ClassMap) this constructor}. + * See the description of the constructor for particular behavior of the copying. + * + * @param c the copied constructor. + * @param declaring the class to which the created method is added. + * @param map the hash table associating original class names + * with substituted names. + * It can be <code>null</code>. + * + * @see CtConstructor#CtConstructor(CtConstructor,CtClass,ClassMap) + */ + public static CtConstructor copy(CtConstructor c, CtClass declaring, + ClassMap map) throws CannotCompileException { + return new CtConstructor(c, declaring, map); + } + + /** + * Creates a default (public) constructor. + * + * <p>The created constructor takes no parameter. It calls + * <code>super()</code>. + */ + public static CtConstructor defaultConstructor(CtClass declaring) + throws CannotCompileException + { + CtConstructor cons = new CtConstructor((CtClass[])null, declaring); + + ConstPool cp = declaring.getClassFile2().getConstPool(); + Bytecode code = new Bytecode(cp, 1, 1); + code.addAload(0); + try { + code.addInvokespecial(declaring.getSuperclass(), + "<init>", "()V"); + } + catch (NotFoundException e) { + throw new CannotCompileException(e); + } + + code.add(Bytecode.RETURN); + + // no need to construct a stack map table. + cons.getMethodInfo2().setCodeAttribute(code.toCodeAttribute()); + return cons; + } + + /** + * Creates a public constructor that only calls a constructor + * in the super class. The created constructor receives parameters + * specified by <code>parameters</code> but calls the super's + * constructor without those parameters (that is, it calls the default + * constructor). + * + * <p>The parameters passed to the created constructor should be + * used for field initialization. <code>CtField.Initializer</code> + * objects implicitly insert initialization code in constructor + * bodies. + * + * @param parameters parameter types + * @param exceptions exception types + * @param declaring the class to which the created constructor + * is added. + * @see CtField.Initializer#byParameter(int) + */ + public static CtConstructor skeleton(CtClass[] parameters, + CtClass[] exceptions, CtClass declaring) + throws CannotCompileException + { + return make(parameters, exceptions, PASS_NONE, + null, null, declaring); + } + + /** + * Creates a public constructor that only calls a constructor + * in the super class. The created constructor receives parameters + * specified by <code>parameters</code> and calls the super's + * constructor with those parameters. + * + * @param parameters parameter types + * @param exceptions exception types + * @param declaring the class to which the created constructor + * is added. + */ + public static CtConstructor make(CtClass[] parameters, + CtClass[] exceptions, CtClass declaring) + throws CannotCompileException + { + return make(parameters, exceptions, PASS_PARAMS, + null, null, declaring); + } + + /** + * Creates a public constructor. + * + * <p>If <code>howto</code> is <code>PASS_PARAMS</code>, + * the created constructor calls the super's constructor with the + * same signature. The superclass must contain + * a constructor taking the same set of parameters as the created one. + * + * <p>If <code>howto</code> is <code>PASS_NONE</code>, + * the created constructor calls the super's default constructor. + * The superclass must contain a constructor taking no parameters. + * + * <p>If <code>howto</code> is <code>PASS_ARRAY</code>, + * the created constructor calls the super's constructor + * with the given parameters in the form of an array of + * <code>Object</code>. The signature of the super's constructor + * must be: + * + * <ul><code>constructor(Object[] params, <type> cvalue) + * </code></ul> + * + * <p>Here, <code>cvalue</code> is the constant value specified + * by <code>cparam</code>. + * + * <p>If <code>cparam</code> is <code>null</code>, the signature + * must be: + * + * <ul><code>constructor(Object[] params)</code></ul> + * + * <p>If <code>body</code> is not null, a copy of that method is + * embedded in the body of the created constructor. + * The embedded method is executed after + * the super's constructor is called and the values of fields are + * initialized. Note that <code>body</code> must not + * be a constructor but a method. + * + * <p>Since the embedded method is wrapped + * in parameter-conversion code + * as in <code>CtNewMethod.wrapped()</code>, + * the constructor parameters are + * passed in the form of an array of <code>Object</code>. + * The method specified by <code>body</code> must have the + * signature shown below: + * + * <ul><code>Object method(Object[] params, <type> cvalue) + * </code></ul> + * + * <p>If <code>cparam</code> is <code>null</code>, the signature + * must be: + * + * <ul><code>Object method(Object[] params)</code></ul> + * + * <p>Although the type of the returned value is <code>Object</code>, + * the value must be always <code>null</code>. + * + * <p><i>Example:</i> + * + * <ul><pre>ClassPool pool = ... ; + * CtClass xclass = pool.makeClass("X"); + * CtMethod method = pool.getMethod("Sample", "m"); + * xclass.setSuperclass(pool.get("Y")); + * CtClass[] argTypes = { CtClass.intType }; + * ConstParameter cparam = ConstParameter.string("test"); + * CtConstructor c = CtNewConstructor.make(argTypes, null, + * PASS_PARAMS, method, cparam, xclass); + * xclass.addConstructor(c);</pre></ul> + * + * <p>where the class <code>Sample</code> is as follows: + * + * <ul><pre>public class Sample { + * public Object m(Object[] args, String msg) { + * System.out.println(msg); + * return null; + * } + * }</pre></ul> + * + * <p>This program produces the following class: + * + * <ul><pre>public class X extends Y { + * public X(int p0) { + * super(p0); + * String msg = "test"; + * Object[] args = new Object[] { p0 }; + * // begin of copied body + * System.out.println(msg); + * Object result = null; + * // end + * } + * }</pre></ul> + * + * @param parameters a list of the parameter types + * @param exceptions a list of the exceptions + * @param howto how to pass parameters to the super-class' + * constructor (<code>PASS_NONE</code>, + * <code>PASS_ARRAY</code>, + * or <code>PASS_PARAMS</code>) + * @param body appended body (may be <code>null</code>). + * It must be not a constructor but a method. + * @param cparam constant parameter (may be <code>null</code>.) + * @param declaring the class to which the created constructor + * is added. + * + * @see CtNewMethod#wrapped(CtClass,String,CtClass[],CtClass[],CtMethod,CtMethod.ConstParameter,CtClass) + */ + public static CtConstructor make(CtClass[] parameters, + CtClass[] exceptions, int howto, + CtMethod body, ConstParameter cparam, + CtClass declaring) + throws CannotCompileException + { + return CtNewWrappedConstructor.wrapped(parameters, exceptions, + howto, body, cparam, declaring); + } +} diff --git a/src/main/javassist/CtNewMethod.java b/src/main/javassist/CtNewMethod.java new file mode 100644 index 0000000..ec2a5fa --- /dev/null +++ b/src/main/javassist/CtNewMethod.java @@ -0,0 +1,470 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist; + +import javassist.bytecode.*; +import javassist.compiler.Javac; +import javassist.compiler.CompileError; +import javassist.CtMethod.ConstParameter; + +/** + * A collection of static methods for creating a <code>CtMethod</code>. + * An instance of this class does not make any sense. + * + * @see CtClass#addMethod(CtMethod) + */ +public class CtNewMethod { + + /** + * Compiles the given source code and creates a method. + * The source code must include not only the method body + * but the whole declaration, for example, + * + * <ul><pre>"public Object id(Object obj) { return obj; }"</pre></ul> + * + * @param src the source text. + * @param declaring the class to which the created method is added. + */ + public static CtMethod make(String src, CtClass declaring) + throws CannotCompileException + { + return make(src, declaring, null, null); + } + + /** + * Compiles the given source code and creates a method. + * The source code must include not only the method body + * but the whole declaration, for example, + * + * <ul><pre>"public Object id(Object obj) { return obj; }"</pre></ul> + * + * <p>If the source code includes <code>$proceed()</code>, then + * it is compiled into a method call on the specified object. + * + * @param src the source text. + * @param declaring the class to which the created method is added. + * @param delegateObj the source text specifying the object + * that is called on by <code>$proceed()</code>. + * @param delegateMethod the name of the method + * that is called by <code>$proceed()</code>. + */ + public static CtMethod make(String src, CtClass declaring, + String delegateObj, String delegateMethod) + throws CannotCompileException + { + Javac compiler = new Javac(declaring); + try { + if (delegateMethod != null) + compiler.recordProceed(delegateObj, delegateMethod); + + CtMember obj = compiler.compile(src); + if (obj instanceof CtMethod) + return (CtMethod)obj; + } + catch (CompileError e) { + throw new CannotCompileException(e); + } + + throw new CannotCompileException("not a method"); + } + + /** + * Creates a public (non-static) method. The created method cannot + * be changed to a static method later. + * + * @param returnType the type of the returned value. + * @param mname the method name. + * @param parameters a list of the parameter types. + * @param exceptions a list of the exception types. + * @param body the source text of the method body. + * It must be a block surrounded by <code>{}</code>. + * If it is <code>null</code>, the created method + * does nothing except returning zero or null. + * @param declaring the class to which the created method is added. + * @see #make(int, CtClass, String, CtClass[], CtClass[], String, CtClass) + */ + public static CtMethod make(CtClass returnType, + String mname, CtClass[] parameters, + CtClass[] exceptions, + String body, CtClass declaring) + throws CannotCompileException + { + return make(Modifier.PUBLIC, returnType, mname, parameters, exceptions, + body, declaring); + } + + /** + * Creates a method. <code>modifiers</code> can contain + * <code>Modifier.STATIC</code>. + * + * @param modifiers access modifiers. + * @param returnType the type of the returned value. + * @param mname the method name. + * @param parameters a list of the parameter types. + * @param exceptions a list of the exception types. + * @param body the source text of the method body. + * It must be a block surrounded by <code>{}</code>. + * If it is <code>null</code>, the created method + * does nothing except returning zero or null. + * @param declaring the class to which the created method is added. + * + * @see Modifier + */ + public static CtMethod make(int modifiers, CtClass returnType, + String mname, CtClass[] parameters, + CtClass[] exceptions, + String body, CtClass declaring) + throws CannotCompileException + { + try { + CtMethod cm + = new CtMethod(returnType, mname, parameters, declaring); + cm.setModifiers(modifiers); + cm.setExceptionTypes(exceptions); + cm.setBody(body); + return cm; + } + catch (NotFoundException e) { + throw new CannotCompileException(e); + } + } + + /** + * Creates a copy of a method. This method is provided for creating + * a new method based on an existing method. + * This is a convenience method for calling + * {@link CtMethod#CtMethod(CtMethod, CtClass, ClassMap) this constructor}. + * See the description of the constructor for particular behavior of the copying. + * + * @param src the source method. + * @param declaring the class to which the created method is added. + * @param map the hash table associating original class names + * with substituted names. + * It can be <code>null</code>. + * + * @see CtMethod#CtMethod(CtMethod,CtClass,ClassMap) + */ + public static CtMethod copy(CtMethod src, CtClass declaring, + ClassMap map) throws CannotCompileException { + return new CtMethod(src, declaring, map); + } + + /** + * Creates a copy of a method with a new name. + * This method is provided for creating + * a new method based on an existing method. + * This is a convenience method for calling + * {@link CtMethod#CtMethod(CtMethod, CtClass, ClassMap) this constructor}. + * See the description of the constructor for particular behavior of the copying. + * + * @param src the source method. + * @param name the name of the created method. + * @param declaring the class to which the created method is added. + * @param map the hash table associating original class names + * with substituted names. + * It can be <code>null</code>. + * + * @see CtMethod#CtMethod(CtMethod,CtClass,ClassMap) + */ + public static CtMethod copy(CtMethod src, String name, CtClass declaring, + ClassMap map) throws CannotCompileException { + CtMethod cm = new CtMethod(src, declaring, map); + cm.setName(name); + return cm; + } + + /** + * Creates a public abstract method. + * + * @param returnType the type of the returned value + * @param mname the method name + * @param parameters a list of the parameter types + * @param exceptions a list of the exception types + * @param declaring the class to which the created method is added. + * + * @see CtMethod#CtMethod(CtClass,String,CtClass[],CtClass) + */ + public static CtMethod abstractMethod(CtClass returnType, + String mname, + CtClass[] parameters, + CtClass[] exceptions, + CtClass declaring) + throws NotFoundException + { + CtMethod cm = new CtMethod(returnType, mname, parameters, declaring); + cm.setExceptionTypes(exceptions); + return cm; + } + + /** + * Creates a public getter method. The getter method returns the value + * of the specified field in the class to which this method is added. + * The created method is initially not static even if the field is + * static. Change the modifiers if the method should be static. + * + * @param methodName the name of the getter + * @param field the field accessed. + */ + public static CtMethod getter(String methodName, CtField field) + throws CannotCompileException + { + FieldInfo finfo = field.getFieldInfo2(); + String fieldType = finfo.getDescriptor(); + String desc = "()" + fieldType; + ConstPool cp = finfo.getConstPool(); + MethodInfo minfo = new MethodInfo(cp, methodName, desc); + minfo.setAccessFlags(AccessFlag.PUBLIC); + + Bytecode code = new Bytecode(cp, 2, 1); + try { + String fieldName = finfo.getName(); + if ((finfo.getAccessFlags() & AccessFlag.STATIC) == 0) { + code.addAload(0); + code.addGetfield(Bytecode.THIS, fieldName, fieldType); + } + else + code.addGetstatic(Bytecode.THIS, fieldName, fieldType); + + code.addReturn(field.getType()); + } + catch (NotFoundException e) { + throw new CannotCompileException(e); + } + + minfo.setCodeAttribute(code.toCodeAttribute()); + return new CtMethod(minfo, field.getDeclaringClass()); + } + + /** + * Creates a public setter method. The setter method assigns the + * value of the first parameter to the specified field + * in the class to which this method is added. + * The created method is not static even if the field is + * static. You may not change it to be static + * by <code>setModifiers()</code> in <code>CtBehavior</code>. + * + * @param methodName the name of the setter + * @param field the field accessed. + */ + public static CtMethod setter(String methodName, CtField field) + throws CannotCompileException + { + FieldInfo finfo = field.getFieldInfo2(); + String fieldType = finfo.getDescriptor(); + String desc = "(" + fieldType + ")V"; + ConstPool cp = finfo.getConstPool(); + MethodInfo minfo = new MethodInfo(cp, methodName, desc); + minfo.setAccessFlags(AccessFlag.PUBLIC); + + Bytecode code = new Bytecode(cp, 3, 3); + try { + String fieldName = finfo.getName(); + if ((finfo.getAccessFlags() & AccessFlag.STATIC) == 0) { + code.addAload(0); + code.addLoad(1, field.getType()); + code.addPutfield(Bytecode.THIS, fieldName, fieldType); + } + else { + code.addLoad(1, field.getType()); + code.addPutstatic(Bytecode.THIS, fieldName, fieldType); + } + + code.addReturn(null); + } + catch (NotFoundException e) { + throw new CannotCompileException(e); + } + + minfo.setCodeAttribute(code.toCodeAttribute()); + return new CtMethod(minfo, field.getDeclaringClass()); + } + + /** + * Creates a method forwarding to a delegate in + * a super class. The created method calls a method specified + * by <code>delegate</code> with all the parameters passed to the + * created method. If the delegate method returns a value, + * the created method returns that value to the caller. + * The delegate method must be declared in a super class. + * + * <p>The following method is an example of the created method. + * + * <ul><pre>int f(int p, int q) { + * return super.f(p, q); + * }</pre></ul> + * + * <p>The name of the created method can be changed by + * <code>setName()</code>. + * + * @param delegate the method that the created method forwards to. + * @param declaring the class to which the created method is + * added. + */ + public static CtMethod delegator(CtMethod delegate, CtClass declaring) + throws CannotCompileException + { + try { + return delegator0(delegate, declaring); + } + catch (NotFoundException e) { + throw new CannotCompileException(e); + } + } + + private static CtMethod delegator0(CtMethod delegate, CtClass declaring) + throws CannotCompileException, NotFoundException + { + MethodInfo deleInfo = delegate.getMethodInfo2(); + String methodName = deleInfo.getName(); + String desc = deleInfo.getDescriptor(); + ConstPool cp = declaring.getClassFile2().getConstPool(); + MethodInfo minfo = new MethodInfo(cp, methodName, desc); + minfo.setAccessFlags(deleInfo.getAccessFlags()); + + ExceptionsAttribute eattr = deleInfo.getExceptionsAttribute(); + if (eattr != null) + minfo.setExceptionsAttribute( + (ExceptionsAttribute)eattr.copy(cp, null)); + + Bytecode code = new Bytecode(cp, 0, 0); + boolean isStatic = Modifier.isStatic(delegate.getModifiers()); + CtClass deleClass = delegate.getDeclaringClass(); + CtClass[] params = delegate.getParameterTypes(); + int s; + if (isStatic) { + s = code.addLoadParameters(params, 0); + code.addInvokestatic(deleClass, methodName, desc); + } + else { + code.addLoad(0, deleClass); + s = code.addLoadParameters(params, 1); + code.addInvokespecial(deleClass, methodName, desc); + } + + code.addReturn(delegate.getReturnType()); + code.setMaxLocals(++s); + code.setMaxStack(s < 2 ? 2 : s); // for a 2-word return value + minfo.setCodeAttribute(code.toCodeAttribute()); + return new CtMethod(minfo, declaring); + } + + /** + * Creates a wrapped method. The wrapped method receives parameters + * in the form of an array of <code>Object</code>. + * + * <p>The body of the created method is a copy of the body of the method + * specified by <code>body</code>. However, it is wrapped in + * parameter-conversion code. + * + * <p>The method specified by <code>body</code> must have this singature: + * + * <ul><code>Object method(Object[] params, <type> cvalue) + * </code></ul> + * + * <p>The type of the <code>cvalue</code> depends on + * <code>constParam</code>. + * If <code>constParam</code> is <code>null</code>, the signature + * must be: + * + * <ul><code>Object method(Object[] params)</code></ul> + * + * <p>The method body copied from <code>body</code> is wrapped in + * parameter-conversion code, which converts parameters specified by + * <code>parameterTypes</code> into an array of <code>Object</code>. + * The returned value is also converted from the <code>Object</code> + * type to the type specified by <code>returnType</code>. Thus, + * the resulting method body is as follows: + * + * <ul><pre>Object[] params = new Object[] { p0, p1, ... }; + * <<i>type</i>> cvalue = <<i>constant-value</i>>; + * <i>... copied method body ...</i> + * Object result = <<i>returned value</i>> + * return (<i><returnType></i>)result; + * </pre></ul> + * + * <p>The variables <code>p0</code>, <code>p2</code>, ... represent + * formal parameters of the created method. + * The value of <code>cvalue</code> is specified by + * <code>constParam</code>. + * + * <p>If the type of a parameter or a returned value is a primitive + * type, then the value is converted into a wrapper object such as + * <code>java.lang.Integer</code>. If the type of the returned value + * is <code>void</code>, the returned value is discarded. + * + * <p><i>Example:</i> + * + * <ul><pre>ClassPool pool = ... ; + * CtClass vec = pool.makeClass("intVector"); + * vec.setSuperclass(pool.get("java.util.Vector")); + * CtMethod addMethod = pool.getMethod("Sample", "add0"); + * + * CtClass[] argTypes = { CtClass.intType }; + * CtMethod m = CtNewMethod.wrapped(CtClass.voidType, "add", argTypes, + * null, addMethod, null, vec); + * vec.addMethod(m);</pre></ul> + * + * <p>where the class <code>Sample</code> is as follows: + * + * <ul><pre>public class Sample extends java.util.Vector { + * public Object add0(Object[] args) { + * super.addElement(args[0]); + * return null; + * } + * }</pre></ul> + * + * <p>This program produces a class <code>intVector</code>: + * + * <ul><pre>public class intVector extends java.util.Vector { + * public void add(int p0) { + * Object[] args = new Object[] { p0 }; + * // begin of the copied body + * super.addElement(args[0]); + * Object result = null; + * // end + * } + * }</pre></ul> + * + * <p>Note that the type of the parameter to <code>add()</code> depends + * only on the value of <code>argTypes</code> passed to + * <code>CtNewMethod.wrapped()</code>. Thus, it is easy to + * modify this program to produce a + * <code>StringVector</code> class, which is a vector containing + * only <code>String</code> objects, and other vector classes. + * + * @param returnType the type of the returned value. + * @param mname the method name. + * @param parameterTypes a list of the parameter types. + * @param exceptionTypes a list of the exception types. + * @param body the method body + * (must not be a static method). + * @param constParam the constant parameter + * (maybe <code>null</code>). + * @param declaring the class to which the created method is + * added. + */ + public static CtMethod wrapped(CtClass returnType, + String mname, + CtClass[] parameterTypes, + CtClass[] exceptionTypes, + CtMethod body, ConstParameter constParam, + CtClass declaring) + throws CannotCompileException + { + return CtNewWrappedMethod.wrapped(returnType, mname, parameterTypes, + exceptionTypes, body, constParam, declaring); + } +} diff --git a/src/main/javassist/CtNewNestedClass.java b/src/main/javassist/CtNewNestedClass.java new file mode 100644 index 0000000..3611443 --- /dev/null +++ b/src/main/javassist/CtNewNestedClass.java @@ -0,0 +1,66 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist; + +import javassist.bytecode.ClassFile; +import javassist.bytecode.AccessFlag; +import javassist.bytecode.InnerClassesAttribute; + +/** + * A newly created public nested class. + */ +class CtNewNestedClass extends CtNewClass { + CtNewNestedClass(String realName, ClassPool cp, boolean isInterface, + CtClass superclass) { + super(realName, cp, isInterface, superclass); + } + + /** + * This method does not change the STATIC bit. The original value is kept. + */ + public void setModifiers(int mod) { + mod = mod & ~Modifier.STATIC; + super.setModifiers(mod); + updateInnerEntry(mod, getName(), this, true); + } + + private static void updateInnerEntry(int mod, String name, CtClass clazz, boolean outer) { + ClassFile cf = clazz.getClassFile2(); + InnerClassesAttribute ica = (InnerClassesAttribute)cf.getAttribute( + InnerClassesAttribute.tag); + if (ica == null) + return; + + int n = ica.tableLength(); + for (int i = 0; i < n; i++) + if (name.equals(ica.innerClass(i))) { + int acc = ica.accessFlags(i) & AccessFlag.STATIC; + ica.setAccessFlags(i, mod | acc); + String outName = ica.outerClass(i); + if (outName != null && outer) + try { + CtClass parent = clazz.getClassPool().get(outName); + updateInnerEntry(mod, name, parent, false); + } + catch (NotFoundException e) { + throw new RuntimeException("cannot find the declaring class: " + + outName); + } + + break; + } + } +} diff --git a/src/main/javassist/CtNewWrappedConstructor.java b/src/main/javassist/CtNewWrappedConstructor.java new file mode 100644 index 0000000..78ab399 --- /dev/null +++ b/src/main/javassist/CtNewWrappedConstructor.java @@ -0,0 +1,101 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist; + +import javassist.bytecode.*; +import javassist.CtMethod.ConstParameter; + +class CtNewWrappedConstructor extends CtNewWrappedMethod { + private static final int PASS_NONE = CtNewConstructor.PASS_NONE; + // private static final int PASS_ARRAY = CtNewConstructor.PASS_ARRAY; + private static final int PASS_PARAMS = CtNewConstructor.PASS_PARAMS; + + public static CtConstructor wrapped(CtClass[] parameterTypes, + CtClass[] exceptionTypes, + int howToCallSuper, + CtMethod body, + ConstParameter constParam, + CtClass declaring) + throws CannotCompileException + { + try { + CtConstructor cons = new CtConstructor(parameterTypes, declaring); + cons.setExceptionTypes(exceptionTypes); + Bytecode code = makeBody(declaring, declaring.getClassFile2(), + howToCallSuper, body, + parameterTypes, constParam); + cons.getMethodInfo2().setCodeAttribute(code.toCodeAttribute()); + return cons; + } + catch (NotFoundException e) { + throw new CannotCompileException(e); + } + } + + protected static Bytecode makeBody(CtClass declaring, ClassFile classfile, + int howToCallSuper, + CtMethod wrappedBody, + CtClass[] parameters, + ConstParameter cparam) + throws CannotCompileException + { + int stacksize, stacksize2; + + int superclazz = classfile.getSuperclassId(); + Bytecode code = new Bytecode(classfile.getConstPool(), 0, 0); + code.setMaxLocals(false, parameters, 0); + code.addAload(0); + if (howToCallSuper == PASS_NONE) { + stacksize = 1; + code.addInvokespecial(superclazz, "<init>", "()V"); + } + else if (howToCallSuper == PASS_PARAMS) { + stacksize = code.addLoadParameters(parameters, 1) + 1; + code.addInvokespecial(superclazz, "<init>", + Descriptor.ofConstructor(parameters)); + } + else { + stacksize = compileParameterList(code, parameters, 1); + String desc; + if (cparam == null) { + stacksize2 = 2; + desc = ConstParameter.defaultConstDescriptor(); + } + else { + stacksize2 = cparam.compile(code) + 2; + desc = cparam.constDescriptor(); + } + + if (stacksize < stacksize2) + stacksize = stacksize2; + + code.addInvokespecial(superclazz, "<init>", desc); + } + + if (wrappedBody == null) + code.add(Bytecode.RETURN); + else { + stacksize2 = makeBody0(declaring, classfile, wrappedBody, + false, parameters, CtClass.voidType, + cparam, code); + if (stacksize < stacksize2) + stacksize = stacksize2; + } + + code.setMaxStack(stacksize); + return code; + } +} diff --git a/src/main/javassist/CtNewWrappedMethod.java b/src/main/javassist/CtNewWrappedMethod.java new file mode 100644 index 0000000..94cabd6 --- /dev/null +++ b/src/main/javassist/CtNewWrappedMethod.java @@ -0,0 +1,196 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist; + +import javassist.bytecode.*; +import javassist.compiler.JvstCodeGen; +import java.util.Hashtable; +import javassist.CtMethod.ConstParameter; + +class CtNewWrappedMethod { + + private static final String addedWrappedMethod = "_added_m$"; + + public static CtMethod wrapped(CtClass returnType, String mname, + CtClass[] parameterTypes, + CtClass[] exceptionTypes, + CtMethod body, ConstParameter constParam, + CtClass declaring) + throws CannotCompileException + { + CtMethod mt = new CtMethod(returnType, mname, parameterTypes, + declaring); + mt.setModifiers(body.getModifiers()); + try { + mt.setExceptionTypes(exceptionTypes); + } + catch (NotFoundException e) { + throw new CannotCompileException(e); + } + + Bytecode code = makeBody(declaring, declaring.getClassFile2(), body, + parameterTypes, returnType, constParam); + mt.getMethodInfo2().setCodeAttribute(code.toCodeAttribute()); + return mt; + } + + static Bytecode makeBody(CtClass clazz, ClassFile classfile, + CtMethod wrappedBody, + CtClass[] parameters, + CtClass returnType, + ConstParameter cparam) + throws CannotCompileException + { + boolean isStatic = Modifier.isStatic(wrappedBody.getModifiers()); + Bytecode code = new Bytecode(classfile.getConstPool(), 0, 0); + int stacksize = makeBody0(clazz, classfile, wrappedBody, isStatic, + parameters, returnType, cparam, code); + code.setMaxStack(stacksize); + code.setMaxLocals(isStatic, parameters, 0); + return code; + } + + /* The generated method body does not need a stack map table + * because it does not contain a branch instruction. + */ + protected static int makeBody0(CtClass clazz, ClassFile classfile, + CtMethod wrappedBody, + boolean isStatic, CtClass[] parameters, + CtClass returnType, ConstParameter cparam, + Bytecode code) + throws CannotCompileException + { + if (!(clazz instanceof CtClassType)) + throw new CannotCompileException("bad declaring class" + + clazz.getName()); + + if (!isStatic) + code.addAload(0); + + int stacksize = compileParameterList(code, parameters, + (isStatic ? 0 : 1)); + int stacksize2; + String desc; + if (cparam == null) { + stacksize2 = 0; + desc = ConstParameter.defaultDescriptor(); + } + else { + stacksize2 = cparam.compile(code); + desc = cparam.descriptor(); + } + + checkSignature(wrappedBody, desc); + + String bodyname; + try { + bodyname = addBodyMethod((CtClassType)clazz, classfile, + wrappedBody); + /* if an exception is thrown below, the method added above + * should be removed. (future work :<) + */ + } + catch (BadBytecode e) { + throw new CannotCompileException(e); + } + + if (isStatic) + code.addInvokestatic(Bytecode.THIS, bodyname, desc); + else + code.addInvokespecial(Bytecode.THIS, bodyname, desc); + + compileReturn(code, returnType); // consumes 2 stack entries + + if (stacksize < stacksize2 + 2) + stacksize = stacksize2 + 2; + + return stacksize; + } + + private static void checkSignature(CtMethod wrappedBody, + String descriptor) + throws CannotCompileException + { + if (!descriptor.equals(wrappedBody.getMethodInfo2().getDescriptor())) + throw new CannotCompileException( + "wrapped method with a bad signature: " + + wrappedBody.getDeclaringClass().getName() + + '.' + wrappedBody.getName()); + } + + private static String addBodyMethod(CtClassType clazz, + ClassFile classfile, + CtMethod src) + throws BadBytecode, CannotCompileException + { + Hashtable bodies = clazz.getHiddenMethods(); + String bodyname = (String)bodies.get(src); + if (bodyname == null) { + do { + bodyname = addedWrappedMethod + clazz.getUniqueNumber(); + } while (classfile.getMethod(bodyname) != null); + ClassMap map = new ClassMap(); + map.put(src.getDeclaringClass().getName(), clazz.getName()); + MethodInfo body = new MethodInfo(classfile.getConstPool(), + bodyname, src.getMethodInfo2(), + map); + int acc = body.getAccessFlags(); + body.setAccessFlags(AccessFlag.setPrivate(acc)); + body.addAttribute(new SyntheticAttribute(classfile.getConstPool())); + // a stack map is copied. rebuilding it is not needed. + classfile.addMethod(body); + bodies.put(src, bodyname); + CtMember.Cache cache = clazz.hasMemberCache(); + if (cache != null) + cache.addMethod(new CtMethod(body, clazz)); + } + + return bodyname; + } + + /* compileParameterList() returns the stack size used + * by the produced code. + * + * @param regno the index of the local variable in which + * the first argument is received. + * (0: static method, 1: regular method.) + */ + static int compileParameterList(Bytecode code, + CtClass[] params, int regno) { + return JvstCodeGen.compileParameterList(code, params, regno); + } + + /* + * The produced codes cosume 1 or 2 stack entries. + */ + private static void compileReturn(Bytecode code, CtClass type) { + if (type.isPrimitive()) { + CtPrimitiveType pt = (CtPrimitiveType)type; + if (pt != CtClass.voidType) { + String wrapper = pt.getWrapperName(); + code.addCheckcast(wrapper); + code.addInvokevirtual(wrapper, pt.getGetMethodName(), + pt.getGetMethodDescriptor()); + } + + code.addOpcode(pt.getReturnOp()); + } + else { + code.addCheckcast(type); + code.addOpcode(Bytecode.ARETURN); + } + } +} diff --git a/src/main/javassist/CtPrimitiveType.java b/src/main/javassist/CtPrimitiveType.java new file mode 100644 index 0000000..faf6700 --- /dev/null +++ b/src/main/javassist/CtPrimitiveType.java @@ -0,0 +1,111 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist; + +/** + * An instance of <code>CtPrimitiveType</code> represents a primitive type. + * It is obtained from <code>CtClass</code>. + */ +public final class CtPrimitiveType extends CtClass { + private char descriptor; + private String wrapperName; + private String getMethodName; + private String mDescriptor; + private int returnOp; + private int arrayType; + private int dataSize; + + CtPrimitiveType(String name, char desc, String wrapper, + String methodName, String mDesc, int opcode, int atype, + int size) { + super(name); + descriptor = desc; + wrapperName = wrapper; + getMethodName = methodName; + mDescriptor = mDesc; + returnOp = opcode; + arrayType = atype; + dataSize = size; + } + + /** + * Returns <code>true</code> if this object represents a primitive + * Java type: boolean, byte, char, short, int, long, float, double, + * or void. + */ + public boolean isPrimitive() { return true; } + + /** + * Returns the modifiers for this type. + * For decoding, use <code>javassist.Modifier</code>. + * + * @see Modifier + */ + public int getModifiers() { + return Modifier.PUBLIC | Modifier.FINAL; + } + + /** + * Returns the descriptor representing this type. + * For example, if the type is int, then the descriptor is I. + */ + public char getDescriptor() { return descriptor; } + + /** + * Returns the name of the wrapper class. + * For example, if the type is int, then the wrapper class is + * <code>java.lang.Integer</code>. + */ + public String getWrapperName() { return wrapperName; } + + /** + * Returns the name of the method for retrieving the value + * from the wrapper object. + * For example, if the type is int, then the method name is + * <code>intValue</code>. + */ + public String getGetMethodName() { return getMethodName; } + + /** + * Returns the descriptor of the method for retrieving the value + * from the wrapper object. + * For example, if the type is int, then the method descriptor is + * <code>()I</code>. + */ + public String getGetMethodDescriptor() { return mDescriptor; } + + /** + * Returns the opcode for returning a value of the type. + * For example, if the type is int, then the returned opcode is + * <code>javassit.bytecode.Opcode.IRETURN</code>. + */ + public int getReturnOp() { return returnOp; } + + /** + * Returns the array-type code representing the type. + * It is used for the newarray instruction. + * For example, if the type is int, then this method returns + * <code>javassit.bytecode.Opcode.T_INT</code>. + */ + public int getArrayType() { return arrayType; } + + /** + * Returns the data size of the primitive type. + * If the type is long or double, this method returns 2. + * Otherwise, it returns 1. + */ + public int getDataSize() { return dataSize; } +} diff --git a/src/main/javassist/Loader.java b/src/main/javassist/Loader.java new file mode 100644 index 0000000..160ef6e --- /dev/null +++ b/src/main/javassist/Loader.java @@ -0,0 +1,446 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist; + +import java.io.*; +import java.util.Hashtable; +import java.util.Vector; +import java.security.ProtectionDomain; + +/** + * The class loader for Javassist. + * + * <p>This is a sample class loader using <code>ClassPool</code>. + * Unlike a regular class loader, this class loader obtains bytecode + * from a <code>ClassPool</code>. + * + * <p>Note that Javassist can be used without this class loader; programmers + * can define their own versions of class loader. They can run + * a program even without any user-defined class loader if that program + * is statically translated with Javassist. + * This class loader is just provided as a utility class. + * + * <p>Suppose that an instance of <code>MyTranslator</code> implementing + * the interface <code>Translator</code> is responsible for modifying + * class files. + * The startup program of an application using <code>MyTranslator</code> + * should be something like this: + * + * <ul><pre> + * import javassist.*; + * + * public class Main { + * public static void main(String[] args) throws Throwable { + * MyTranslator myTrans = new MyTranslator(); + * ClassPool cp = ClassPool.getDefault(); + * Loader cl = new Loader(cp); + * cl.addTranslator(cp, myTrans); + * cl.run("MyApp", args); + * } + * } + * </pre></ul> + * + * <p>Class <code>MyApp</code> is the main program of the application. + * + * <p>This program should be executed as follows: + * + * <ul><pre> + * % java Main <i>arg1</i> <i>arg2</i>... + * </pre></ul> + * + * <p>It modifies the class <code>MyApp</code> with a <code>MyTranslator</code> + * object before the JVM loads it. + * Then it calls <code>main()</code> in <code>MyApp</code> with arguments + * <i>arg1</i>, <i>arg2</i>, ... + * + * <p>This program execution is equivalent to: + * + * <ul><pre> + * % java MyApp <i>arg1</i> <i>arg2</i>... + * </pre></ul> + * + * <p>except that classes are translated by <code>MyTranslator</code> + * at load time. + * + * <p>If only a particular class must be modified when it is loaded, + * the startup program can be simpler; <code>MyTranslator</code> is + * unnecessary. For example, if only a class <code>test.Rectangle</code> + * is modified, the <code>main()</code> method above will be the following: + * + * <ul><pre> + * ClassPool cp = ClassPool.getDefault(); + * Loader cl = new Loader(cp); + * CtClass ct = cp.get("test.Rectangle"); + * ct.setSuperclass(cp.get("test.Point")); + * cl.run("MyApp", args);</pre></ul> + * + * <p>This program changes the super class of the <code>test.Rectangle</code> + * class. + * + * <p><b>Note 1:</b> + * + * <p>This class loader does not allow the users to intercept the loading + * of <code>java.*</code> and <code>javax.*</code> classes (and + * <code>sun.*</code>, <code>org.xml.*</code>, ...) unless + * <code>Loader.doDelegation</code> is <code>false</code>. This is because + * the JVM prohibits a user class loader from loading a system class. + * Also see Note 2. + * If this behavior is not appropriate, a subclass of <code>Loader</code> + * must be defined and <code>loadClassByDelegation()</code> must be overridden. + * + * <p><b>Note 2:</b> + * + * <p>If classes are loaded with different class loaders, they belong to + * separate name spaces. If class <code>C</code> is loaded by a class + * loader <code>CL</code>, all classes that the class <code>C</code> + * refers to are also loaded by <code>CL</code>. However, if <code>CL</code> + * delegates the loading of the class <code>C</code> to <code>CL'</code>, + * then those classes that the class <code>C</code> refers to + * are loaded by a parent class loader <code>CL'</code> + * instead of <code>CL</code>. + * + * <p>If an object of class <code>C</code> is assigned + * to a variable of class <code>C</code> belonging to a different name + * space, then a <code>ClassCastException</code> is thrown. + * + * <p>Because of the fact above, this loader delegates only the loading of + * <code>javassist.Loader</code> + * and classes included in package <code>java.*</code> and + * <code>javax.*</code> to the parent class + * loader. Other classes are directly loaded by this loader. + * + * <p>For example, suppose that <code>java.lang.String</code> would be loaded + * by this loader while <code>java.io.File</code> is loaded by the parent + * class loader. If the constructor of <code>java.io.File</code> is called + * with an instance of <code>java.lang.String</code>, then it may throw + * an exception since it accepts an instance of only the + * <code>java.lang.String</code> loaded by the parent class loader. + * + * @see javassist.ClassPool + * @see javassist.Translator + */ +public class Loader extends ClassLoader { + private Hashtable notDefinedHere; // must be atomic. + private Vector notDefinedPackages; // must be atomic. + private ClassPool source; + private Translator translator; + private ProtectionDomain domain; + + /** + * Specifies the algorithm of class loading. + * + * <p>This class loader uses the parent class loader for + * <code>java.*</code> and <code>javax.*</code> classes. + * If this variable <code>doDelegation</code> + * is <code>false</code>, this class loader does not delegate those + * classes to the parent class loader. + * + * <p>The default value is <code>true</code>. + */ + public boolean doDelegation = true; + + /** + * Creates a new class loader. + */ + public Loader() { + this(null); + } + + /** + * Creates a new class loader. + * + * @param cp the source of class files. + */ + public Loader(ClassPool cp) { + init(cp); + } + + /** + * Creates a new class loader + * using the specified parent class loader for delegation. + * + * @param parent the parent class loader. + * @param cp the source of class files. + */ + public Loader(ClassLoader parent, ClassPool cp) { + super(parent); + init(cp); + } + + private void init(ClassPool cp) { + notDefinedHere = new Hashtable(); + notDefinedPackages = new Vector(); + source = cp; + translator = null; + domain = null; + delegateLoadingOf("javassist.Loader"); + } + + /** + * Records a class so that the loading of that class is delegated + * to the parent class loader. + * + * <p>If the given class name ends with <code>.</code> (dot), then + * that name is interpreted as a package name. All the classes + * in that package and the sub packages are delegated. + */ + public void delegateLoadingOf(String classname) { + if (classname.endsWith(".")) + notDefinedPackages.addElement(classname); + else + notDefinedHere.put(classname, this); + } + + /** + * Sets the protection domain for the classes handled by this class + * loader. Without registering an appropriate protection domain, + * the program loaded by this loader will not work with a security + * manager or a signed jar file. + */ + public void setDomain(ProtectionDomain d) { + domain = d; + } + + /** + * Sets the soruce <code>ClassPool</code>. + */ + public void setClassPool(ClassPool cp) { + source = cp; + } + + /** + * Adds a translator, which is called whenever a class is loaded. + * + * @param cp the <code>ClassPool</code> object for obtaining + * a class file. + * @param t a translator. + * @throws NotFoundException if <code>t.start()</code> throws an exception. + * @throws CannotCompileException if <code>t.start()</code> throws an exception. + */ + public void addTranslator(ClassPool cp, Translator t) + throws NotFoundException, CannotCompileException { + source = cp; + translator = t; + t.start(cp); + } + + /** + * Loads a class with an instance of <code>Loader</code> + * and calls <code>main()</code> of that class. + * + * <p>This method calls <code>run()</code>. + * + * @param args command line parameters. + * <ul> + * <code>args[0]</code> is the class name to be loaded. + * <br><code>args[1..n]</code> are parameters passed + * to the target <code>main()</code>. + * </ul> + * + * @see javassist.Loader#run(String[]) + */ + public static void main(String[] args) throws Throwable { + Loader cl = new Loader(); + cl.run(args); + } + + /** + * Loads a class and calls <code>main()</code> in that class. + * + * @param args command line parameters. + * <ul> + * <code>args[0]</code> is the class name to be loaded. + * <br><code>args[1..n]</code> are parameters passed + * to the target <code>main()</code>. + * </ul> + */ + public void run(String[] args) throws Throwable { + int n = args.length - 1; + if (n >= 0) { + String[] args2 = new String[n]; + for (int i = 0; i < n; ++i) + args2[i] = args[i + 1]; + + run(args[0], args2); + } + } + + /** + * Loads a class and calls <code>main()</code> in that class. + * + * @param classname the loaded class. + * @param args parameters passed to <code>main()</code>. + */ + public void run(String classname, String[] args) throws Throwable { + Class c = loadClass(classname); + try { + c.getDeclaredMethod("main", new Class[] { String[].class }).invoke( + null, + new Object[] { args }); + } + catch (java.lang.reflect.InvocationTargetException e) { + throw e.getTargetException(); + } + } + + /** + * Requests the class loader to load a class. + */ + protected Class loadClass(String name, boolean resolve) + throws ClassFormatError, ClassNotFoundException { + name = name.intern(); + synchronized (name) { + Class c = findLoadedClass(name); + if (c == null) + c = loadClassByDelegation(name); + + if (c == null) + c = findClass(name); + + if (c == null) + c = delegateToParent(name); + + if (resolve) + resolveClass(c); + + return c; + } + } + + /** + * Finds the specified class using <code>ClassPath</code>. + * If the source throws an exception, this returns null. + * + * <p>This method can be overridden by a subclass of + * <code>Loader</code>. Note that the overridden method must not throw + * an exception when it just fails to find a class file. + * + * @return null if the specified class could not be found. + * @throws ClassNotFoundException if an exception is thrown while + * obtaining a class file. + */ + protected Class findClass(String name) throws ClassNotFoundException { + byte[] classfile; + try { + if (source != null) { + if (translator != null) + translator.onLoad(source, name); + + try { + classfile = source.get(name).toBytecode(); + } + catch (NotFoundException e) { + return null; + } + } + else { + String jarname = "/" + name.replace('.', '/') + ".class"; + InputStream in = this.getClass().getResourceAsStream(jarname); + if (in == null) + return null; + + classfile = ClassPoolTail.readStream(in); + } + } + catch (Exception e) { + throw new ClassNotFoundException( + "caught an exception while obtaining a class file for " + + name, e); + } + + int i = name.lastIndexOf('.'); + if (i != -1) { + String pname = name.substring(0, i); + if (getPackage(pname) == null) + try { + definePackage( + pname, null, null, null, null, null, null, null); + } + catch (IllegalArgumentException e) { + // ignore. maybe the package object for the same + // name has been created just right away. + } + } + + if (domain == null) + return defineClass(name, classfile, 0, classfile.length); + else + return defineClass(name, classfile, 0, classfile.length, domain); + } + + protected Class loadClassByDelegation(String name) + throws ClassNotFoundException + { + /* The swing components must be loaded by a system + * class loader. + * javax.swing.UIManager loads a (concrete) subclass + * of LookAndFeel by a system class loader and cast + * an instance of the class to LookAndFeel for + * (maybe) a security reason. To avoid failure of + * type conversion, LookAndFeel must not be loaded + * by this class loader. + */ + + Class c = null; + if (doDelegation) + if (name.startsWith("java.") + || name.startsWith("javax.") + || name.startsWith("sun.") + || name.startsWith("com.sun.") + || name.startsWith("org.w3c.") + || name.startsWith("org.xml.") + || notDelegated(name)) + c = delegateToParent(name); + + return c; + } + + private boolean notDelegated(String name) { + if (notDefinedHere.get(name) != null) + return true; + + int n = notDefinedPackages.size(); + for (int i = 0; i < n; ++i) + if (name.startsWith((String)notDefinedPackages.elementAt(i))) + return true; + + return false; + } + + protected Class delegateToParent(String classname) + throws ClassNotFoundException + { + ClassLoader cl = getParent(); + if (cl != null) + return cl.loadClass(classname); + else + return findSystemClass(classname); + } + + protected Package getPackage(String name) { + return super.getPackage(name); + } + /* + // Package p = super.getPackage(name); + Package p = null; + if (p == null) + return definePackage(name, null, null, null, + null, null, null, null); + else + return p; + } + */ +} diff --git a/src/main/javassist/LoaderClassPath.java b/src/main/javassist/LoaderClassPath.java new file mode 100644 index 0000000..5ecb02e --- /dev/null +++ b/src/main/javassist/LoaderClassPath.java @@ -0,0 +1,95 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist; + +import java.io.InputStream; +import java.net.URL; +import java.lang.ref.WeakReference; + +/** + * A class search-path representing a class loader. + * + * <p>It is used for obtaining a class file from the given + * class loader by <code>getResourceAsStream()</code>. + * The <code>LoaderClassPath</code> refers to the class loader through + * <code>WeakReference</code>. If the class loader is garbage collected, + * the other search pathes are examined. + * + * <p>The given class loader must have both <code>getResourceAsStream()</code> + * and <code>getResource()</code>. + * + * @author <a href="mailto:bill@jboss.org">Bill Burke</a> + * @author Shigeru Chiba + * + * @see ClassPool#insertClassPath(ClassPath) + * @see ClassPool#appendClassPath(ClassPath) + * @see ClassClassPath + */ +public class LoaderClassPath implements ClassPath { + private WeakReference clref; + + /** + * Creates a search path representing a class loader. + */ + public LoaderClassPath(ClassLoader cl) { + clref = new WeakReference(cl); + } + + public String toString() { + Object cl = null; + if (clref != null) + cl = clref.get(); + + return cl == null ? "<null>" : cl.toString(); + } + + /** + * Obtains a class file from the class loader. + * This method calls <code>getResourceAsStream(String)</code> + * on the class loader. + */ + public InputStream openClassfile(String classname) { + String cname = classname.replace('.', '/') + ".class"; + ClassLoader cl = (ClassLoader)clref.get(); + if (cl == null) + return null; // not found + else + return cl.getResourceAsStream(cname); + } + + /** + * Obtains the URL of the specified class file. + * This method calls <code>getResource(String)</code> + * on the class loader. + * + * @return null if the class file could not be found. + */ + public URL find(String classname) { + String cname = classname.replace('.', '/') + ".class"; + ClassLoader cl = (ClassLoader)clref.get(); + if (cl == null) + return null; // not found + else + return cl.getResource(cname); + } + + /** + * Closes this class path. + */ + public void close() { + clref = null; + } +} diff --git a/src/main/javassist/Modifier.java b/src/main/javassist/Modifier.java new file mode 100644 index 0000000..c1b30d6 --- /dev/null +++ b/src/main/javassist/Modifier.java @@ -0,0 +1,218 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist; + +import javassist.bytecode.AccessFlag; + +/** + * The Modifier class provides static methods and constants to decode + * class and member access modifiers. The constant values are equivalent + * to the corresponding values in <code>javassist.bytecode.AccessFlag</code>. + * + * <p>All the methods/constants in this class are compatible with + * ones in <code>java.lang.reflect.Modifier</code>. + * + * @see CtClass#getModifiers() + */ +public class Modifier { + public static final int PUBLIC = AccessFlag.PUBLIC; + public static final int PRIVATE = AccessFlag.PRIVATE; + public static final int PROTECTED = AccessFlag.PROTECTED; + public static final int STATIC = AccessFlag.STATIC; + public static final int FINAL = AccessFlag.FINAL; + public static final int SYNCHRONIZED = AccessFlag.SYNCHRONIZED; + public static final int VOLATILE = AccessFlag.VOLATILE; + public static final int VARARGS = AccessFlag.VARARGS; + public static final int TRANSIENT = AccessFlag.TRANSIENT; + public static final int NATIVE = AccessFlag.NATIVE; + public static final int INTERFACE = AccessFlag.INTERFACE; + public static final int ABSTRACT = AccessFlag.ABSTRACT; + public static final int STRICT = AccessFlag.STRICT; + public static final int ANNOTATION = AccessFlag.ANNOTATION; + public static final int ENUM = AccessFlag.ENUM; + + /** + * Returns true if the modifiers include the <tt>public</tt> + * modifier. + */ + public static boolean isPublic(int mod) { + return (mod & PUBLIC) != 0; + } + + /** + * Returns true if the modifiers include the <tt>private</tt> + * modifier. + */ + public static boolean isPrivate(int mod) { + return (mod & PRIVATE) != 0; + } + + /** + * Returns true if the modifiers include the <tt>protected</tt> + * modifier. + */ + public static boolean isProtected(int mod) { + return (mod & PROTECTED) != 0; + } + + /** + * Returns true if the modifiers do not include either + * <tt>public</tt>, <tt>protected</tt>, or <tt>private</tt>. + */ + public static boolean isPackage(int mod) { + return (mod & (PUBLIC | PRIVATE | PROTECTED)) == 0; + } + + /** + * Returns true if the modifiers include the <tt>static</tt> + * modifier. + */ + public static boolean isStatic(int mod) { + return (mod & STATIC) != 0; + } + + /** + * Returns true if the modifiers include the <tt>final</tt> + * modifier. + */ + public static boolean isFinal(int mod) { + return (mod & FINAL) != 0; + } + + /** + * Returns true if the modifiers include the <tt>synchronized</tt> + * modifier. + */ + public static boolean isSynchronized(int mod) { + return (mod & SYNCHRONIZED) != 0; + } + + /** + * Returns true if the modifiers include the <tt>volatile</tt> + * modifier. + */ + public static boolean isVolatile(int mod) { + return (mod & VOLATILE) != 0; + } + + /** + * Returns true if the modifiers include the <tt>transient</tt> + * modifier. + */ + public static boolean isTransient(int mod) { + return (mod & TRANSIENT) != 0; + } + + /** + * Returns true if the modifiers include the <tt>native</tt> + * modifier. + */ + public static boolean isNative(int mod) { + return (mod & NATIVE) != 0; + } + + /** + * Returns true if the modifiers include the <tt>interface</tt> + * modifier. + */ + public static boolean isInterface(int mod) { + return (mod & INTERFACE) != 0; + } + + /** + * Returns true if the modifiers include the <tt>annotation</tt> + * modifier. + * + * @since 3.2 + */ + public static boolean isAnnotation(int mod) { + return (mod & ANNOTATION) != 0; + } + + /** + * Returns true if the modifiers include the <tt>enum</tt> + * modifier. + * + * @since 3.2 + */ + public static boolean isEnum(int mod) { + return (mod & ENUM) != 0; + } + + /** + * Returns true if the modifiers include the <tt>abstract</tt> + * modifier. + */ + public static boolean isAbstract(int mod) { + return (mod & ABSTRACT) != 0; + } + + /** + * Returns true if the modifiers include the <tt>strictfp</tt> + * modifier. + */ + public static boolean isStrict(int mod) { + return (mod & STRICT) != 0; + } + + /** + * Truns the public bit on. The protected and private bits are + * cleared. + */ + public static int setPublic(int mod) { + return (mod & ~(PRIVATE | PROTECTED)) | PUBLIC; + } + + /** + * Truns the protected bit on. The protected and public bits are + * cleared. + */ + public static int setProtected(int mod) { + return (mod & ~(PRIVATE | PUBLIC)) | PROTECTED; + } + + /** + * Truns the private bit on. The protected and private bits are + * cleared. + */ + public static int setPrivate(int mod) { + return (mod & ~(PROTECTED | PUBLIC)) | PRIVATE; + } + + /** + * Clears the public, protected, and private bits. + */ + public static int setPackage(int mod) { + return (mod & ~(PROTECTED | PUBLIC | PRIVATE)); + } + + /** + * Clears a specified bit in <code>mod</code>. + */ + public static int clear(int mod, int clearBit) { + return mod & ~clearBit; + } + + /** + * Return a string describing the access modifier flags in + * the specified modifier. + * + * @param mod modifier flags. + */ + public static String toString(int mod) { + return java.lang.reflect.Modifier.toString(mod); + } +} diff --git a/src/main/javassist/NotFoundException.java b/src/main/javassist/NotFoundException.java new file mode 100644 index 0000000..61de140 --- /dev/null +++ b/src/main/javassist/NotFoundException.java @@ -0,0 +1,29 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist; + +/** + * Signals that something could not be found. + */ +public class NotFoundException extends Exception { + public NotFoundException(String msg) { + super(msg); + } + + public NotFoundException(String msg, Exception e) { + super(msg + " because of " + e.toString()); + } +} diff --git a/src/main/javassist/SerialVersionUID.java b/src/main/javassist/SerialVersionUID.java new file mode 100644 index 0000000..5e62310 --- /dev/null +++ b/src/main/javassist/SerialVersionUID.java @@ -0,0 +1,210 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist; + +import java.io.*; +import java.lang.reflect.Modifier; + +import javassist.bytecode.*; +import java.util.*; +import java.security.*; + +/** + * Utility for calculating serialVersionUIDs for Serializable classes. + * + * @author Bob Lee (crazybob@crazybob.org) + * @author modified by Shigeru Chiba + */ +public class SerialVersionUID { + + /** + * Adds serialVersionUID if one does not already exist. Call this before + * modifying a class to maintain serialization compatability. + */ + public static void setSerialVersionUID(CtClass clazz) + throws CannotCompileException, NotFoundException + { + // check for pre-existing field. + try { + clazz.getDeclaredField("serialVersionUID"); + return; + } + catch (NotFoundException e) {} + + // check if the class is serializable. + if (!isSerializable(clazz)) + return; + + // add field with default value. + CtField field = new CtField(CtClass.longType, "serialVersionUID", + clazz); + field.setModifiers(Modifier.PRIVATE | Modifier.STATIC | + Modifier.FINAL); + clazz.addField(field, calculateDefault(clazz) + "L"); + } + + /** + * Does the class implement Serializable? + */ + private static boolean isSerializable(CtClass clazz) + throws NotFoundException + { + ClassPool pool = clazz.getClassPool(); + return clazz.subtypeOf(pool.get("java.io.Serializable")); + } + + /** + * Calculate default value. See Java Serialization Specification, Stream + * Unique Identifiers. + */ + static long calculateDefault(CtClass clazz) + throws CannotCompileException + { + try { + ByteArrayOutputStream bout = new ByteArrayOutputStream(); + DataOutputStream out = new DataOutputStream(bout); + ClassFile classFile = clazz.getClassFile(); + + // class name. + String javaName = javaName(clazz); + out.writeUTF(javaName); + + CtMethod[] methods = clazz.getDeclaredMethods(); + + // class modifiers. + int classMods = clazz.getModifiers(); + if ((classMods & Modifier.INTERFACE) != 0) + if (methods.length > 0) + classMods = classMods | Modifier.ABSTRACT; + else + classMods = classMods & ~Modifier.ABSTRACT; + + out.writeInt(classMods); + + // interfaces. + String[] interfaces = classFile.getInterfaces(); + for (int i = 0; i < interfaces.length; i++) + interfaces[i] = javaName(interfaces[i]); + + Arrays.sort(interfaces); + for (int i = 0; i < interfaces.length; i++) + out.writeUTF(interfaces[i]); + + // fields. + CtField[] fields = clazz.getDeclaredFields(); + Arrays.sort(fields, new Comparator() { + public int compare(Object o1, Object o2) { + CtField field1 = (CtField)o1; + CtField field2 = (CtField)o2; + return field1.getName().compareTo(field2.getName()); + } + }); + + for (int i = 0; i < fields.length; i++) { + CtField field = (CtField) fields[i]; + int mods = field.getModifiers(); + if (((mods & Modifier.PRIVATE) == 0) || + ((mods & (Modifier.STATIC | Modifier.TRANSIENT)) == 0)) { + out.writeUTF(field.getName()); + out.writeInt(mods); + out.writeUTF(field.getFieldInfo2().getDescriptor()); + } + } + + // static initializer. + if (classFile.getStaticInitializer() != null) { + out.writeUTF("<clinit>"); + out.writeInt(Modifier.STATIC); + out.writeUTF("()V"); + } + + // constructors. + CtConstructor[] constructors = clazz.getDeclaredConstructors(); + Arrays.sort(constructors, new Comparator() { + public int compare(Object o1, Object o2) { + CtConstructor c1 = (CtConstructor)o1; + CtConstructor c2 = (CtConstructor)o2; + return c1.getMethodInfo2().getDescriptor().compareTo( + c2.getMethodInfo2().getDescriptor()); + } + }); + + for (int i = 0; i < constructors.length; i++) { + CtConstructor constructor = constructors[i]; + int mods = constructor.getModifiers(); + if ((mods & Modifier.PRIVATE) == 0) { + out.writeUTF("<init>"); + out.writeInt(mods); + out.writeUTF(constructor.getMethodInfo2() + .getDescriptor().replace('/', '.')); + } + } + + // methods. + Arrays.sort(methods, new Comparator() { + public int compare(Object o1, Object o2) { + CtMethod m1 = (CtMethod)o1; + CtMethod m2 = (CtMethod)o2; + int value = m1.getName().compareTo(m2.getName()); + if (value == 0) + value = m1.getMethodInfo2().getDescriptor() + .compareTo(m2.getMethodInfo2().getDescriptor()); + + return value; + } + }); + + for (int i = 0; i < methods.length; i++) { + CtMethod method = methods[i]; + int mods = method.getModifiers() + & (Modifier.PUBLIC | Modifier.PRIVATE + | Modifier.PROTECTED | Modifier.STATIC + | Modifier.FINAL | Modifier.SYNCHRONIZED + | Modifier.NATIVE | Modifier.ABSTRACT | Modifier.STRICT); + if ((mods & Modifier.PRIVATE) == 0) { + out.writeUTF(method.getName()); + out.writeInt(mods); + out.writeUTF(method.getMethodInfo2() + .getDescriptor().replace('/', '.')); + } + } + + // calculate hash. + out.flush(); + MessageDigest digest = MessageDigest.getInstance("SHA"); + byte[] digested = digest.digest(bout.toByteArray()); + long hash = 0; + for (int i = Math.min(digested.length, 8) - 1; i >= 0; i--) + hash = (hash << 8) | (digested[i] & 0xFF); + + return hash; + } + catch (IOException e) { + throw new CannotCompileException(e); + } + catch (NoSuchAlgorithmException e) { + throw new CannotCompileException(e); + } + } + + private static String javaName(CtClass clazz) { + return Descriptor.toJavaName(Descriptor.toJvmName(clazz)); + } + + private static String javaName(String name) { + return Descriptor.toJavaName(Descriptor.toJvmName(name)); + } +} diff --git a/src/main/javassist/Translator.java b/src/main/javassist/Translator.java new file mode 100644 index 0000000..ea44034 --- /dev/null +++ b/src/main/javassist/Translator.java @@ -0,0 +1,70 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist; + +/** + * An observer of <code>Loader</code>. + * The users can define a class implementing this + * interface and attach an instance of that class to a + * <code>Loader</code> object so that it can translate a class file + * when the class file is loaded into the JVM. + * + * @see Loader#addTranslator(ClassPool, Translator) + */ +public interface Translator { + /** + * Is invoked by a <code>Loader</code> for initialization + * when the object is attached to the <code>Loader</code> object. + * This method can be used for getting (for caching) some + * <code>CtClass</code> objects that will be accessed + * in <code>onLoad()</code> in <code>Translator</code>. + * + * @param pool the <code>ClassPool</code> that this translator + * should use. + * @see Loader + * @throws NotFoundException if a <code>CtClass</code> cannot be found. + * @throws CannotCompileException if the initialization by this method + * fails. + */ + void start(ClassPool pool) + throws NotFoundException, CannotCompileException; + + /** + * Is invoked by a <code>Loader</code> for notifying that + * a class is loaded. The <code>Loader</code> calls + * + * <ul><pre> + * pool.get(classname).toBytecode()</pre></ul> + * + * to read the class file after <code>onLoad()</code> returns. + * + * <p><code>classname</code> may be the name of a class + * that has not been created yet. + * If so, <code>onLoad()</code> must create that class so that + * the <code>Loader</code> can read it after <code>onLoad()</code> + * returns. + * + * @param pool the <code>ClassPool</code> that this translator + * should use. + * @param classname the name of the class being loaded. + * @see Loader + * @throws NotFoundException if a <code>CtClass</code> cannot be found. + * @throws CannotCompileException if the code transformation + * by this method fails. + */ + void onLoad(ClassPool pool, String classname) + throws NotFoundException, CannotCompileException; +} diff --git a/src/main/javassist/URLClassPath.java b/src/main/javassist/URLClassPath.java new file mode 100644 index 0000000..0cdb820 --- /dev/null +++ b/src/main/javassist/URLClassPath.java @@ -0,0 +1,178 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist; + +import java.io.*; +import java.net.*; + +/** + * A class search-path specified with URL (http). + * + * @see javassist.ClassPath + * @see ClassPool#insertClassPath(ClassPath) + * @see ClassPool#appendClassPath(ClassPath) + */ +public class URLClassPath implements ClassPath { + protected String hostname; + protected int port; + protected String directory; + protected String packageName; + + /** + * Creates a search path specified with URL (http). + * + * <p>This search path is used only if a requested + * class name starts with the name specified by <code>packageName</code>. + * If <code>packageName</code> is "org.javassist." and a requested class is + * "org.javassist.test.Main", then the given URL is used for loading that class. + * The <code>URLClassPath</code> obtains a class file from: + * + * <ul><pre>http://www.javassist.org:80/java/classes/org/javassist/test/Main.class + * </pre></ul> + * + * <p>Here, we assume that <code>host</code> is "www.javassist.org", + * <code>port</code> is 80, and <code>directory</code> is "/java/classes/". + * + * <p>If <code>packageName</code> is <code>null</code>, the URL is used + * for loading any class. + * + * @param host host name + * @param port port number + * @param directory directory name ending with "/". + * It can be "/" (root directory). + * It must start with "/". + * @param packageName package name. It must end with "." (dot). + */ + public URLClassPath(String host, int port, + String directory, String packageName) { + hostname = host; + this.port = port; + this.directory = directory; + this.packageName = packageName; + } + + public String toString() { + return hostname + ":" + port + directory; + } + + /** + * Opens a class file with http. + * + * @return null if the class file could not be found. + */ + public InputStream openClassfile(String classname) { + try { + URLConnection con = openClassfile0(classname); + if (con != null) + return con.getInputStream(); + } + catch (IOException e) {} + return null; // not found + } + + private URLConnection openClassfile0(String classname) throws IOException { + if (packageName == null || classname.startsWith(packageName)) { + String jarname + = directory + classname.replace('.', '/') + ".class"; + return fetchClass0(hostname, port, jarname); + } + else + return null; // not found + } + + /** + * Returns the URL. + * + * @return null if the class file could not be obtained. + */ + public URL find(String classname) { + try { + URLConnection con = openClassfile0(classname); + InputStream is = con.getInputStream(); + if (is != null) { + is.close(); + return con.getURL(); + } + } + catch (IOException e) {} + return null; + } + + /** + * Closes this class path. + */ + public void close() {} + + /** + * Reads a class file on an http server. + * + * @param host host name + * @param port port number + * @param directory directory name ending with "/". + * It can be "/" (root directory). + * It must start with "/". + * @param classname fully-qualified class name + */ + public static byte[] fetchClass(String host, int port, + String directory, String classname) + throws IOException + { + byte[] b; + URLConnection con = fetchClass0(host, port, + directory + classname.replace('.', '/') + ".class"); + int size = con.getContentLength(); + InputStream s = con.getInputStream(); + try { + if (size <= 0) + b = ClassPoolTail.readStream(s); + else { + b = new byte[size]; + int len = 0; + do { + int n = s.read(b, len, size - len); + if (n < 0) + throw new IOException("the stream was closed: " + + classname); + + len += n; + } while (len < size); + } + } + finally { + s.close(); + } + + return b; + } + + private static URLConnection fetchClass0(String host, int port, + String filename) + throws IOException + { + URL url; + try { + url = new URL("http", host, port, filename); + } + catch (MalformedURLException e) { + // should never reache here. + throw new IOException("invalid URL?"); + } + + URLConnection con = url.openConnection(); + con.connect(); + return con; + } +} diff --git a/src/main/javassist/bytecode/AccessFlag.java b/src/main/javassist/bytecode/AccessFlag.java new file mode 100644 index 0000000..6dda112 --- /dev/null +++ b/src/main/javassist/bytecode/AccessFlag.java @@ -0,0 +1,132 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.bytecode; + +/** + * A support class providing static methods and constants + * for access modifiers such as public, rivate, ... + */ +public class AccessFlag { + public static final int PUBLIC = 0x0001; + public static final int PRIVATE = 0x0002; + public static final int PROTECTED = 0x0004; + public static final int STATIC = 0x0008; + public static final int FINAL = 0x0010; + public static final int SYNCHRONIZED = 0x0020; + public static final int VOLATILE = 0x0040; + public static final int BRIDGE = 0x0040; // for method_info + public static final int TRANSIENT = 0x0080; + public static final int VARARGS = 0x0080; // for method_info + public static final int NATIVE = 0x0100; + public static final int INTERFACE = 0x0200; + public static final int ABSTRACT = 0x0400; + public static final int STRICT = 0x0800; + public static final int SYNTHETIC = 0x1000; + public static final int ANNOTATION = 0x2000; + public static final int ENUM = 0x4000; + + public static final int SUPER = 0x0020; + + // Note: 0x0020 is assigned to both ACC_SUPER and ACC_SYNCHRONIZED + // although java.lang.reflect.Modifier does not recognize ACC_SUPER. + + /** + * Truns the public bit on. The protected and private bits are + * cleared. + */ + public static int setPublic(int accflags) { + return (accflags & ~(PRIVATE | PROTECTED)) | PUBLIC; + } + + /** + * Truns the protected bit on. The protected and public bits are + * cleared. + */ + public static int setProtected(int accflags) { + return (accflags & ~(PRIVATE | PUBLIC)) | PROTECTED; + } + + /** + * Truns the private bit on. The protected and private bits are + * cleared. + */ + public static int setPrivate(int accflags) { + return (accflags & ~(PROTECTED | PUBLIC)) | PRIVATE; + } + + /** + * Clears the public, protected, and private bits. + */ + public static int setPackage(int accflags) { + return (accflags & ~(PROTECTED | PUBLIC | PRIVATE)); + } + + /** + * Returns true if the access flags include the public bit. + */ + public static boolean isPublic(int accflags) { + return (accflags & PUBLIC) != 0; + } + + /** + * Returns true if the access flags include the protected bit. + */ + public static boolean isProtected(int accflags) { + return (accflags & PROTECTED) != 0; + } + + /** + * Returns true if the access flags include the private bit. + */ + public static boolean isPrivate(int accflags) { + return (accflags & PRIVATE) != 0; + } + + /** + * Returns true if the access flags include neither public, protected, + * or private. + */ + public static boolean isPackage(int accflags) { + return (accflags & (PROTECTED | PUBLIC | PRIVATE)) == 0; + } + + /** + * Clears a specified bit in <code>accflags</code>. + */ + public static int clear(int accflags, int clearBit) { + return accflags & ~clearBit; + } + + /** + * Converts a javassist.Modifier into + * a javassist.bytecode.AccessFlag. + * + * @param modifier javassist.Modifier + */ + public static int of(int modifier) { + return modifier; + } + + /** + * Converts a javassist.bytecode.AccessFlag + * into a javassist.Modifier. + * + * @param accflags javassist.bytecode.Accessflag + */ + public static int toModifier(int accflags) { + return accflags; + } +} diff --git a/src/main/javassist/bytecode/AnnotationDefaultAttribute.java b/src/main/javassist/bytecode/AnnotationDefaultAttribute.java new file mode 100644 index 0000000..d591ac8 --- /dev/null +++ b/src/main/javassist/bytecode/AnnotationDefaultAttribute.java @@ -0,0 +1,159 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.bytecode; + +import javassist.CtClass; +import javassist.bytecode.annotation.AnnotationsWriter; +import javassist.bytecode.annotation.MemberValue; + +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.IOException; +import java.util.Map; + +/** + * A class representing <code>AnnotationDefault_attribute</code>. + * + * <p>For example, if you declare the following annotation type: + * + * <ul><pre> + * @interface Author { + * String name() default "Shakespeare"; + * int age() default 99; + * } + * </pre></ul> + * + * <p>The defautl values of <code>name</code> and <code>age</code> + * are stored as annotation default attributes in <code>Author.class</code>. + * The following code snippet obtains the default value of <code>name</code>: + * + * <ul><pre> + * ClassPool pool = ... + * CtClass cc = pool.get("Author"); + * CtMethod cm = cc.getDeclaredMethod("age"); + * MethodInfo minfo = cm.getMethodInfo(); + * AnnotationDefaultAttribute ada + * = (AnnotationDefaultAttribute) + * minfo.getAttribute(AnnotationDefaultAttribute.tag); + * MemberValue value = ada.getDefaultValue()); // default value of age + * </pre></ul> + * + * <p>If the following statement is executed after the code above, + * the default value of age is set to 80: + * + * <ul><pre> + * ada.setDefaultValue(new IntegerMemberValue(minfo.getConstPool(), 80)); + * </pre></ul> + * + * @see AnnotationsAttribute + * @see javassist.bytecode.annotation.MemberValue + */ + +public class AnnotationDefaultAttribute extends AttributeInfo { + /** + * The name of the <code>AnnotationDefault</code> attribute. + */ + public static final String tag = "AnnotationDefault"; + + /** + * Constructs an <code>AnnotationDefault_attribute</code>. + * + * @param cp constant pool + * @param info the contents of this attribute. It does not + * include <code>attribute_name_index</code> or + * <code>attribute_length</code>. + */ + public AnnotationDefaultAttribute(ConstPool cp, byte[] info) { + super(cp, tag, info); + } + + /** + * Constructs an empty <code>AnnotationDefault_attribute</code>. + * The default value can be set by <code>setDefaultValue()</code>. + * + * @param cp constant pool + * @see #setDefaultValue(javassist.bytecode.annotation.MemberValue) + */ + public AnnotationDefaultAttribute(ConstPool cp) { + this(cp, new byte[] { 0, 0 }); + } + + /** + * @param n the attribute name. + */ + AnnotationDefaultAttribute(ConstPool cp, int n, DataInputStream in) + throws IOException + { + super(cp, n, in); + } + + /** + * Copies this attribute and returns a new copy. + */ + public AttributeInfo copy(ConstPool newCp, Map classnames) { + AnnotationsAttribute.Copier copier + = new AnnotationsAttribute.Copier(info, constPool, newCp, classnames); + try { + copier.memberValue(0); + return new AnnotationDefaultAttribute(newCp, copier.close()); + } + catch (Exception e) { + throw new RuntimeException(e.toString()); + } + } + + /** + * Obtains the default value represented by this attribute. + */ + public MemberValue getDefaultValue() + { + try { + return new AnnotationsAttribute.Parser(info, constPool) + .parseMemberValue(); + } + catch (Exception e) { + throw new RuntimeException(e.toString()); + } + } + + /** + * Changes the default value represented by this attribute. + * + * @param value the new value. + * @see javassist.bytecode.annotation.Annotation#createMemberValue(ConstPool, CtClass) + */ + public void setDefaultValue(MemberValue value) { + ByteArrayOutputStream output = new ByteArrayOutputStream(); + AnnotationsWriter writer = new AnnotationsWriter(output, constPool); + try { + value.write(writer); + writer.close(); + } + catch (IOException e) { + throw new RuntimeException(e); // should never reach here. + } + + set(output.toByteArray()); + + } + + /** + * Returns a string representation of this object. + */ + public String toString() { + return getDefaultValue().toString(); + } +} diff --git a/src/main/javassist/bytecode/AnnotationsAttribute.java b/src/main/javassist/bytecode/AnnotationsAttribute.java new file mode 100644 index 0000000..0d2ac09 --- /dev/null +++ b/src/main/javassist/bytecode/AnnotationsAttribute.java @@ -0,0 +1,701 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.bytecode; + +import java.util.Map; +import java.util.HashMap; +import java.io.IOException; +import java.io.DataInputStream; +import java.io.ByteArrayOutputStream; +import javassist.bytecode.annotation.*; + +/** + * A class representing + * <code>RuntimeVisibleAnnotations_attribute</code> and + * <code>RuntimeInvisibleAnnotations_attribute</code>. + * + * <p>To obtain an AnnotationAttribute object, invoke + * <code>getAttribute(AnnotationsAttribute.visibleTag)</code> + * in <code>ClassFile</code>, <code>MethodInfo</code>, + * or <code>FieldInfo</code>. The obtained attribute is a + * runtime visible annotations attribute. + * If the parameter is + * <code>AnnotationAttribute.invisibleTag</code>, then the obtained + * attribute is a runtime invisible one. + * + * <p>For example, + * + * <ul><pre> + * import javassist.bytecode.annotation.Annotation; + * : + * CtMethod m = ... ; + * MethodInfo minfo = m.getMethodInfo(); + * AnnotationsAttribute attr = (AnnotationsAttribute) + * minfo.getAttribute(AnnotationsAttribute.invisibleTag); + * Annotation an = attr.getAnnotation("Author"); + * String s = ((StringMemberValue)an.getMemberValue("name")).getValue(); + * System.out.println("@Author(name=" + s + ")"); + * </pre></ul> + * + * <p>This code snippet retrieves an annotation of the type <code>Author</code> + * from the <code>MethodInfo</code> object specified by <code>minfo</code>. + * Then, it prints the value of <code>name</code> in <code>Author</code>. + * + * <p>If the annotation type <code>Author</code> is annotated by a meta annotation: + * + * <ul><pre> + * @Retention(RetentionPolicy.RUNTIME) + * </pre></ul> + * + * <p>Then <code>Author</code> is visible at runtime. Therefore, the third + * statement of the code snippet above must be changed into: + * + * <ul><pre> + * AnnotationsAttribute attr = (AnnotationsAttribute) + * minfo.getAttribute(AnnotationsAttribute.visibleTag); + * </pre></ul> + * + * <p>The attribute tag must be <code>visibleTag</code> instead of + * <code>invisibleTag</code>. + * + * <p>If the member value of an annotation is not specified, the default value + * is used as that member value. If so, <code>getMemberValue()</code> in + * <code>Annotation</code> returns <code>null</code> + * since the default value is not included in the + * <code>AnnotationsAttribute</code>. It is included in the + * <code>AnnotationDefaultAttribute</code> of the method declared in the + * annotation type. + * + * <p>If you want to record a new AnnotationAttribute object, execute the + * following snippet: + * + * <ul><pre> + * ClassFile cf = ... ; + * ConstPool cp = cf.getConstPool(); + * AnnotationsAttribute attr + * = new AnnotationsAttribute(cp, AnnotationsAttribute.visibleTag); + * Annotation a = new Annotation("Author", cp); + * a.addMemberValue("name", new StringMemberValue("Chiba", cp)); + * attr.setAnnotation(a); + * cf.addAttribute(attr); + * cf.setVersionToJava5(); + * </pre></ul> + * + * <p>The last statement is necessary if the class file was produced by + * Javassist or JDK 1.4. Otherwise, it is not necessary. + * + * @see AnnotationDefaultAttribute + * @see javassist.bytecode.annotation.Annotation + */ +public class AnnotationsAttribute extends AttributeInfo { + /** + * The name of the <code>RuntimeVisibleAnnotations</code> attribute. + */ + public static final String visibleTag = "RuntimeVisibleAnnotations"; + + /** + * The name of the <code>RuntimeInvisibleAnnotations</code> attribute. + */ + public static final String invisibleTag = "RuntimeInvisibleAnnotations"; + + /** + * Constructs a <code>Runtime(In)VisibleAnnotations_attribute</code>. + * + * @param cp constant pool + * @param attrname attribute name (<code>visibleTag</code> or + * <code>invisibleTag</code>). + * @param info the contents of this attribute. It does not + * include <code>attribute_name_index</code> or + * <code>attribute_length</code>. + */ + public AnnotationsAttribute(ConstPool cp, String attrname, byte[] info) { + super(cp, attrname, info); + } + + /** + * Constructs an empty + * <code>Runtime(In)VisibleAnnotations_attribute</code>. + * A new annotation can be later added to the created attribute + * by <code>setAnnotations()</code>. + * + * @param cp constant pool + * @param attrname attribute name (<code>visibleTag</code> or + * <code>invisibleTag</code>). + * @see #setAnnotations(Annotation[]) + */ + public AnnotationsAttribute(ConstPool cp, String attrname) { + this(cp, attrname, new byte[] { 0, 0 }); + } + + /** + * @param n the attribute name. + */ + AnnotationsAttribute(ConstPool cp, int n, DataInputStream in) + throws IOException + { + super(cp, n, in); + } + + /** + * Returns <code>num_annotations</code>. + */ + public int numAnnotations() { + return ByteArray.readU16bit(info, 0); + } + + /** + * Copies this attribute and returns a new copy. + */ + public AttributeInfo copy(ConstPool newCp, Map classnames) { + Copier copier = new Copier(info, constPool, newCp, classnames); + try { + copier.annotationArray(); + return new AnnotationsAttribute(newCp, getName(), copier.close()); + } + catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * Parses the annotations and returns a data structure representing + * the annotation with the specified type. See also + * <code>getAnnotations()</code> as to the returned data structure. + * + * @param type the annotation type. + * @return null if the specified annotation type is not included. + * @see #getAnnotations() + */ + public Annotation getAnnotation(String type) { + Annotation[] annotations = getAnnotations(); + for (int i = 0; i < annotations.length; i++) { + if (annotations[i].getTypeName().equals(type)) + return annotations[i]; + } + + return null; + } + + /** + * Adds an annotation. If there is an annotation with the same type, + * it is removed before the new annotation is added. + * + * @param annotation the added annotation. + */ + public void addAnnotation(Annotation annotation) { + String type = annotation.getTypeName(); + Annotation[] annotations = getAnnotations(); + for (int i = 0; i < annotations.length; i++) { + if (annotations[i].getTypeName().equals(type)) { + annotations[i] = annotation; + setAnnotations(annotations); + return; + } + } + + Annotation[] newlist = new Annotation[annotations.length + 1]; + System.arraycopy(annotations, 0, newlist, 0, annotations.length); + newlist[annotations.length] = annotation; + setAnnotations(newlist); + } + + /** + * Parses the annotations and returns a data structure representing + * that parsed annotations. Note that changes of the node values of the + * returned tree are not reflected on the annotations represented by + * this object unless the tree is copied back to this object by + * <code>setAnnotations()</code>. + * + * @see #setAnnotations(Annotation[]) + */ + public Annotation[] getAnnotations() { + try { + return new Parser(info, constPool).parseAnnotations(); + } + catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * Changes the annotations represented by this object according to + * the given array of <code>Annotation</code> objects. + * + * @param annotations the data structure representing the + * new annotations. + */ + public void setAnnotations(Annotation[] annotations) { + ByteArrayOutputStream output = new ByteArrayOutputStream(); + AnnotationsWriter writer = new AnnotationsWriter(output, constPool); + try { + int n = annotations.length; + writer.numAnnotations(n); + for (int i = 0; i < n; ++i) + annotations[i].write(writer); + + writer.close(); + } + catch (IOException e) { + throw new RuntimeException(e); // should never reach here. + } + + set(output.toByteArray()); + } + + /** + * Changes the annotations. A call to this method is equivalent to: + * <ul><pre>setAnnotations(new Annotation[] { annotation })</pre></ul> + * + * @param annotation the data structure representing + * the new annotation. + */ + public void setAnnotation(Annotation annotation) { + setAnnotations(new Annotation[] { annotation }); + } + + /** + * @param oldname a JVM class name. + * @param newname a JVM class name. + */ + void renameClass(String oldname, String newname) { + HashMap map = new HashMap(); + map.put(oldname, newname); + renameClass(map); + } + + void renameClass(Map classnames) { + Renamer renamer = new Renamer(info, getConstPool(), classnames); + try { + renamer.annotationArray(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + void getRefClasses(Map classnames) { renameClass(classnames); } + + /** + * Returns a string representation of this object. + */ + public String toString() { + Annotation[] a = getAnnotations(); + StringBuilder sbuf = new StringBuilder(); + int i = 0; + while (i < a.length) { + sbuf.append(a[i++].toString()); + if (i != a.length) + sbuf.append(", "); + } + + return sbuf.toString(); + } + + static class Walker { + byte[] info; + + Walker(byte[] attrInfo) { + info = attrInfo; + } + + final void parameters() throws Exception { + int numParam = info[0] & 0xff; + parameters(numParam, 1); + } + + void parameters(int numParam, int pos) throws Exception { + for (int i = 0; i < numParam; ++i) + pos = annotationArray(pos); + } + + final void annotationArray() throws Exception { + annotationArray(0); + } + + final int annotationArray(int pos) throws Exception { + int num = ByteArray.readU16bit(info, pos); + return annotationArray(pos + 2, num); + } + + int annotationArray(int pos, int num) throws Exception { + for (int i = 0; i < num; ++i) + pos = annotation(pos); + + return pos; + } + + final int annotation(int pos) throws Exception { + int type = ByteArray.readU16bit(info, pos); + int numPairs = ByteArray.readU16bit(info, pos + 2); + return annotation(pos + 4, type, numPairs); + } + + int annotation(int pos, int type, int numPairs) throws Exception { + for (int j = 0; j < numPairs; ++j) + pos = memberValuePair(pos); + + return pos; + } + + final int memberValuePair(int pos) throws Exception { + int nameIndex = ByteArray.readU16bit(info, pos); + return memberValuePair(pos + 2, nameIndex); + } + + int memberValuePair(int pos, int nameIndex) throws Exception { + return memberValue(pos); + } + + final int memberValue(int pos) throws Exception { + int tag = info[pos] & 0xff; + if (tag == 'e') { + int typeNameIndex = ByteArray.readU16bit(info, pos + 1); + int constNameIndex = ByteArray.readU16bit(info, pos + 3); + enumMemberValue(pos, typeNameIndex, constNameIndex); + return pos + 5; + } + else if (tag == 'c') { + int index = ByteArray.readU16bit(info, pos + 1); + classMemberValue(pos, index); + return pos + 3; + } + else if (tag == '@') + return annotationMemberValue(pos + 1); + else if (tag == '[') { + int num = ByteArray.readU16bit(info, pos + 1); + return arrayMemberValue(pos + 3, num); + } + else { // primitive types or String. + int index = ByteArray.readU16bit(info, pos + 1); + constValueMember(tag, index); + return pos + 3; + } + } + + void constValueMember(int tag, int index) throws Exception {} + + void enumMemberValue(int pos, int typeNameIndex, int constNameIndex) + throws Exception { + } + + void classMemberValue(int pos, int index) throws Exception {} + + int annotationMemberValue(int pos) throws Exception { + return annotation(pos); + } + + int arrayMemberValue(int pos, int num) throws Exception { + for (int i = 0; i < num; ++i) { + pos = memberValue(pos); + } + + return pos; + } + } + + static class Renamer extends Walker { + ConstPool cpool; + Map classnames; + + /** + * Constructs a renamer. It renames some class names + * into the new names specified by <code>map</code>. + * + * @param info the annotations attribute. + * @param cp the constant pool. + * @param map pairs of replaced and substituted class names. + * It can be null. + */ + Renamer(byte[] info, ConstPool cp, Map map) { + super(info); + cpool = cp; + classnames = map; + } + + int annotation(int pos, int type, int numPairs) throws Exception { + renameType(pos - 4, type); + return super.annotation(pos, type, numPairs); + } + + void enumMemberValue(int pos, int typeNameIndex, int constNameIndex) + throws Exception + { + renameType(pos + 1, typeNameIndex); + super.enumMemberValue(pos, typeNameIndex, constNameIndex); + } + + void classMemberValue(int pos, int index) throws Exception { + renameType(pos + 1, index); + super.classMemberValue(pos, index); + } + + private void renameType(int pos, int index) { + String name = cpool.getUtf8Info(index); + String newName = Descriptor.rename(name, classnames); + if (!name.equals(newName)) { + int index2 = cpool.addUtf8Info(newName); + ByteArray.write16bit(index2, info, pos); + } + } + } + + static class Copier extends Walker { + ByteArrayOutputStream output; + AnnotationsWriter writer; + ConstPool srcPool, destPool; + Map classnames; + + /** + * Constructs a copier. This copier renames some class names + * into the new names specified by <code>map</code> when it copies + * an annotation attribute. + * + * @param info the source attribute. + * @param src the constant pool of the source class. + * @param dest the constant pool of the destination class. + * @param map pairs of replaced and substituted class names. + * It can be null. + */ + Copier(byte[] info, ConstPool src, ConstPool dest, Map map) { + super(info); + output = new ByteArrayOutputStream(); + writer = new AnnotationsWriter(output, dest); + srcPool = src; + destPool = dest; + classnames = map; + } + + byte[] close() throws IOException { + writer.close(); + return output.toByteArray(); + } + + void parameters(int numParam, int pos) throws Exception { + writer.numParameters(numParam); + super.parameters(numParam, pos); + } + + int annotationArray(int pos, int num) throws Exception { + writer.numAnnotations(num); + return super.annotationArray(pos, num); + } + + int annotation(int pos, int type, int numPairs) throws Exception { + writer.annotation(copyType(type), numPairs); + return super.annotation(pos, type, numPairs); + } + + int memberValuePair(int pos, int nameIndex) throws Exception { + writer.memberValuePair(copy(nameIndex)); + return super.memberValuePair(pos, nameIndex); + } + + void constValueMember(int tag, int index) throws Exception { + writer.constValueIndex(tag, copy(index)); + super.constValueMember(tag, index); + } + + void enumMemberValue(int pos, int typeNameIndex, int constNameIndex) + throws Exception + { + writer.enumConstValue(copyType(typeNameIndex), copy(constNameIndex)); + super.enumMemberValue(pos, typeNameIndex, constNameIndex); + } + + void classMemberValue(int pos, int index) throws Exception { + writer.classInfoIndex(copyType(index)); + super.classMemberValue(pos, index); + } + + int annotationMemberValue(int pos) throws Exception { + writer.annotationValue(); + return super.annotationMemberValue(pos); + } + + int arrayMemberValue(int pos, int num) throws Exception { + writer.arrayValue(num); + return super.arrayMemberValue(pos, num); + } + + /** + * Copies a constant pool entry into the destination constant pool + * and returns the index of the copied entry. + * + * @param srcIndex the index of the copied entry into the source + * constant pool. + * @return the index of the copied item into the destination + * constant pool. + */ + int copy(int srcIndex) { + return srcPool.copy(srcIndex, destPool, classnames); + } + + /** + * Copies a constant pool entry into the destination constant pool + * and returns the index of the copied entry. That entry must be + * a Utf8Info representing a class name in the L<class name>; form. + * + * @param srcIndex the index of the copied entry into the source + * constant pool. + * @return the index of the copied item into the destination + * constant pool. + */ + int copyType(int srcIndex) { + String name = srcPool.getUtf8Info(srcIndex); + String newName = Descriptor.rename(name, classnames); + return destPool.addUtf8Info(newName); + } + } + + static class Parser extends Walker { + ConstPool pool; + Annotation[][] allParams; // all parameters + Annotation[] allAnno; // all annotations + Annotation currentAnno; // current annotation + MemberValue currentMember; // current member + + /** + * Constructs a parser. This parser constructs a parse tree of + * the annotations. + * + * @param info the attribute. + * @param src the constant pool. + */ + Parser(byte[] info, ConstPool cp) { + super(info); + pool = cp; + } + + Annotation[][] parseParameters() throws Exception { + parameters(); + return allParams; + } + + Annotation[] parseAnnotations() throws Exception { + annotationArray(); + return allAnno; + } + + MemberValue parseMemberValue() throws Exception { + memberValue(0); + return currentMember; + } + + void parameters(int numParam, int pos) throws Exception { + Annotation[][] params = new Annotation[numParam][]; + for (int i = 0; i < numParam; ++i) { + pos = annotationArray(pos); + params[i] = allAnno; + } + + allParams = params; + } + + int annotationArray(int pos, int num) throws Exception { + Annotation[] array = new Annotation[num]; + for (int i = 0; i < num; ++i) { + pos = annotation(pos); + array[i] = currentAnno; + } + + allAnno = array; + return pos; + } + + int annotation(int pos, int type, int numPairs) throws Exception { + currentAnno = new Annotation(type, pool); + return super.annotation(pos, type, numPairs); + } + + int memberValuePair(int pos, int nameIndex) throws Exception { + pos = super.memberValuePair(pos, nameIndex); + currentAnno.addMemberValue(nameIndex, currentMember); + return pos; + } + + void constValueMember(int tag, int index) throws Exception { + MemberValue m; + ConstPool cp = pool; + switch (tag) { + case 'B' : + m = new ByteMemberValue(index, cp); + break; + case 'C' : + m = new CharMemberValue(index, cp); + break; + case 'D' : + m = new DoubleMemberValue(index, cp); + break; + case 'F' : + m = new FloatMemberValue(index, cp); + break; + case 'I' : + m = new IntegerMemberValue(index, cp); + break; + case 'J' : + m = new LongMemberValue(index, cp); + break; + case 'S' : + m = new ShortMemberValue(index, cp); + break; + case 'Z' : + m = new BooleanMemberValue(index, cp); + break; + case 's' : + m = new StringMemberValue(index, cp); + break; + default : + throw new RuntimeException("unknown tag:" + tag); + } + + currentMember = m; + super.constValueMember(tag, index); + } + + void enumMemberValue(int pos, int typeNameIndex, int constNameIndex) + throws Exception + { + currentMember = new EnumMemberValue(typeNameIndex, + constNameIndex, pool); + super.enumMemberValue(pos, typeNameIndex, constNameIndex); + } + + void classMemberValue(int pos, int index) throws Exception { + currentMember = new ClassMemberValue(index, pool); + super.classMemberValue(pos, index); + } + + int annotationMemberValue(int pos) throws Exception { + Annotation anno = currentAnno; + pos = super.annotationMemberValue(pos); + currentMember = new AnnotationMemberValue(currentAnno, pool); + currentAnno = anno; + return pos; + } + + int arrayMemberValue(int pos, int num) throws Exception { + ArrayMemberValue amv = new ArrayMemberValue(pool); + MemberValue[] elements = new MemberValue[num]; + for (int i = 0; i < num; ++i) { + pos = memberValue(pos); + elements[i] = currentMember; + } + + amv.setValue(elements); + currentMember = amv; + return pos; + } + } +} diff --git a/src/main/javassist/bytecode/AttributeInfo.java b/src/main/javassist/bytecode/AttributeInfo.java new file mode 100644 index 0000000..c5da7e1 --- /dev/null +++ b/src/main/javassist/bytecode/AttributeInfo.java @@ -0,0 +1,286 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.bytecode; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.Map; +import java.util.ArrayList; +import java.util.ListIterator; +import java.util.List; +import java.util.Iterator; + +// Note: if you define a new subclass of AttributeInfo, then +// update AttributeInfo.read(), .copy(), and (maybe) write(). + +/** + * <code>attribute_info</code> structure. + */ +public class AttributeInfo { + protected ConstPool constPool; + int name; + byte[] info; + + protected AttributeInfo(ConstPool cp, int attrname, byte[] attrinfo) { + constPool = cp; + name = attrname; + info = attrinfo; + } + + protected AttributeInfo(ConstPool cp, String attrname) { + this(cp, attrname, (byte[])null); + } + + /** + * Constructs an <code>attribute_info</code> structure. + * + * @param cp constant pool table + * @param attrname attribute name + * @param attrinfo <code>info</code> field + * of <code>attribute_info</code> structure. + */ + public AttributeInfo(ConstPool cp, String attrname, byte[] attrinfo) { + this(cp, cp.addUtf8Info(attrname), attrinfo); + } + + protected AttributeInfo(ConstPool cp, int n, DataInputStream in) + throws IOException + { + constPool = cp; + name = n; + int len = in.readInt(); + info = new byte[len]; + if (len > 0) + in.readFully(info); + } + + static AttributeInfo read(ConstPool cp, DataInputStream in) + throws IOException + { + int name = in.readUnsignedShort(); + String nameStr = cp.getUtf8Info(name); + if (nameStr.charAt(0) < 'L') { + if (nameStr.equals(AnnotationDefaultAttribute.tag)) + return new AnnotationDefaultAttribute(cp, name, in); + else if (nameStr.equals(CodeAttribute.tag)) + return new CodeAttribute(cp, name, in); + else if (nameStr.equals(ConstantAttribute.tag)) + return new ConstantAttribute(cp, name, in); + else if (nameStr.equals(DeprecatedAttribute.tag)) + return new DeprecatedAttribute(cp, name, in); + else if (nameStr.equals(EnclosingMethodAttribute.tag)) + return new EnclosingMethodAttribute(cp, name, in); + else if (nameStr.equals(ExceptionsAttribute.tag)) + return new ExceptionsAttribute(cp, name, in); + else if (nameStr.equals(InnerClassesAttribute.tag)) + return new InnerClassesAttribute(cp, name, in); + } + else { + /* Note that the names of Annotations attributes begin with 'R'. + */ + if (nameStr.equals(LineNumberAttribute.tag)) + return new LineNumberAttribute(cp, name, in); + else if (nameStr.equals(LocalVariableAttribute.tag)) + return new LocalVariableAttribute(cp, name, in); + else if (nameStr.equals(LocalVariableTypeAttribute.tag)) + return new LocalVariableTypeAttribute(cp, name, in); + else if (nameStr.equals(AnnotationsAttribute.visibleTag) + || nameStr.equals(AnnotationsAttribute.invisibleTag)) { + // RuntimeVisibleAnnotations or RuntimeInvisibleAnnotations + return new AnnotationsAttribute(cp, name, in); + } + else if (nameStr.equals(ParameterAnnotationsAttribute.visibleTag) + || nameStr.equals(ParameterAnnotationsAttribute.invisibleTag)) + return new ParameterAnnotationsAttribute(cp, name, in); + else if (nameStr.equals(SignatureAttribute.tag)) + return new SignatureAttribute(cp, name, in); + else if (nameStr.equals(SourceFileAttribute.tag)) + return new SourceFileAttribute(cp, name, in); + else if (nameStr.equals(SyntheticAttribute.tag)) + return new SyntheticAttribute(cp, name, in); + else if (nameStr.equals(StackMap.tag)) + return new StackMap(cp, name, in); + else if (nameStr.equals(StackMapTable.tag)) + return new StackMapTable(cp, name, in); + } + + return new AttributeInfo(cp, name, in); + } + + /** + * Returns an attribute name. + */ + public String getName() { + return constPool.getUtf8Info(name); + } + + /** + * Returns a constant pool table. + */ + public ConstPool getConstPool() { return constPool; } + + /** + * Returns the length of this <code>attribute_info</code> + * structure. + * The returned value is <code>attribute_length + 6</code>. + */ + public int length() { + return info.length + 6; + } + + /** + * Returns the <code>info</code> field + * of this <code>attribute_info</code> structure. + * + * <p>This method is not available if the object is an instance + * of <code>CodeAttribute</code>. + */ + public byte[] get() { return info; } + + /** + * Sets the <code>info</code> field + * of this <code>attribute_info</code> structure. + * + * <p>This method is not available if the object is an instance + * of <code>CodeAttribute</code>. + */ + public void set(byte[] newinfo) { info = newinfo; } + + /** + * Makes a copy. Class names are replaced according to the + * given <code>Map</code> object. + * + * @param newCp the constant pool table used by the new copy. + * @param classnames pairs of replaced and substituted + * class names. + */ + public AttributeInfo copy(ConstPool newCp, Map classnames) { + int s = info.length; + byte[] srcInfo = info; + byte[] newInfo = new byte[s]; + for (int i = 0; i < s; ++i) + newInfo[i] = srcInfo[i]; + + return new AttributeInfo(newCp, getName(), newInfo); + } + + void write(DataOutputStream out) throws IOException { + out.writeShort(name); + out.writeInt(info.length); + if (info.length > 0) + out.write(info); + } + + static int getLength(ArrayList list) { + int size = 0; + int n = list.size(); + for (int i = 0; i < n; ++i) { + AttributeInfo attr = (AttributeInfo)list.get(i); + size += attr.length(); + } + + return size; + } + + static AttributeInfo lookup(ArrayList list, String name) { + if (list == null) + return null; + + ListIterator iterator = list.listIterator(); + while (iterator.hasNext()) { + AttributeInfo ai = (AttributeInfo)iterator.next(); + if (ai.getName().equals(name)) + return ai; + } + + return null; // no such attribute + } + + static synchronized void remove(ArrayList list, String name) { + if (list == null) + return; + + ListIterator iterator = list.listIterator(); + while (iterator.hasNext()) { + AttributeInfo ai = (AttributeInfo)iterator.next(); + if (ai.getName().equals(name)) + iterator.remove(); + } + } + + static void writeAll(ArrayList list, DataOutputStream out) + throws IOException + { + if (list == null) + return; + + int n = list.size(); + for (int i = 0; i < n; ++i) { + AttributeInfo attr = (AttributeInfo)list.get(i); + attr.write(out); + } + } + + static ArrayList copyAll(ArrayList list, ConstPool cp) { + if (list == null) + return null; + + ArrayList newList = new ArrayList(); + int n = list.size(); + for (int i = 0; i < n; ++i) { + AttributeInfo attr = (AttributeInfo)list.get(i); + newList.add(attr.copy(cp, null)); + } + + return newList; + } + + /* The following two methods are used to implement + * ClassFile.renameClass(). + * Only CodeAttribute, LocalVariableAttribute, + * AnnotationsAttribute, and SignatureAttribute + * override these methods. + */ + void renameClass(String oldname, String newname) {} + void renameClass(Map classnames) {} + + static void renameClass(List attributes, String oldname, String newname) { + Iterator iterator = attributes.iterator(); + while (iterator.hasNext()) { + AttributeInfo ai = (AttributeInfo)iterator.next(); + ai.renameClass(oldname, newname); + } + } + + static void renameClass(List attributes, Map classnames) { + Iterator iterator = attributes.iterator(); + while (iterator.hasNext()) { + AttributeInfo ai = (AttributeInfo)iterator.next(); + ai.renameClass(classnames); + } + } + + void getRefClasses(Map classnames) {} + + static void getRefClasses(List attributes, Map classnames) { + Iterator iterator = attributes.iterator(); + while (iterator.hasNext()) { + AttributeInfo ai = (AttributeInfo)iterator.next(); + ai.getRefClasses(classnames); + } + } +} diff --git a/src/main/javassist/bytecode/BadBytecode.java b/src/main/javassist/bytecode/BadBytecode.java new file mode 100644 index 0000000..2f93b52 --- /dev/null +++ b/src/main/javassist/bytecode/BadBytecode.java @@ -0,0 +1,33 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.bytecode; + +/** + * Signals that a bad bytecode sequence has been found. + */ +public class BadBytecode extends Exception { + public BadBytecode(int opcode) { + super("bytecode " + opcode); + } + + public BadBytecode(String msg) { + super(msg); + } + + public BadBytecode(String msg, Throwable cause) { + super(msg, cause); + } +} diff --git a/src/main/javassist/bytecode/ByteArray.java b/src/main/javassist/bytecode/ByteArray.java new file mode 100644 index 0000000..e564822 --- /dev/null +++ b/src/main/javassist/bytecode/ByteArray.java @@ -0,0 +1,76 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.bytecode; + +/** + * A collection of static methods for reading and writing a byte array. + */ +public class ByteArray { + /** + * Reads an unsigned 16bit integer at the index. + */ + public static int readU16bit(byte[] code, int index) { + return ((code[index] & 0xff) << 8) | (code[index + 1] & 0xff); + } + + /** + * Reads a signed 16bit integer at the index. + */ + public static int readS16bit(byte[] code, int index) { + return (code[index] << 8) | (code[index + 1] & 0xff); + } + + /** + * Writes a 16bit integer at the index. + */ + public static void write16bit(int value, byte[] code, int index) { + code[index] = (byte)(value >>> 8); + code[index + 1] = (byte)value; + } + + /** + * Reads a 32bit integer at the index. + */ + public static int read32bit(byte[] code, int index) { + return (code[index] << 24) | ((code[index + 1] & 0xff) << 16) + | ((code[index + 2] & 0xff) << 8) | (code[index + 3] & 0xff); + } + + /** + * Writes a 32bit integer at the index. + */ + public static void write32bit(int value, byte[] code, int index) { + code[index] = (byte)(value >>> 24); + code[index + 1] = (byte)(value >>> 16); + code[index + 2] = (byte)(value >>> 8); + code[index + 3] = (byte)value; + } + + /** + * Copies a 32bit integer. + * + * @param src the source byte array. + * @param isrc the index into the source byte array. + * @param dest the destination byte array. + * @param idest the index into the destination byte array. + */ + static void copy32bit(byte[] src, int isrc, byte[] dest, int idest) { + dest[idest] = src[isrc]; + dest[idest + 1] = src[isrc + 1]; + dest[idest + 2] = src[isrc + 2]; + dest[idest + 3] = src[isrc + 3]; + } +} diff --git a/src/main/javassist/bytecode/ByteStream.java b/src/main/javassist/bytecode/ByteStream.java new file mode 100644 index 0000000..82b30c9 --- /dev/null +++ b/src/main/javassist/bytecode/ByteStream.java @@ -0,0 +1,193 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2010 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.bytecode; + +import java.io.OutputStream; +import java.io.IOException; + +final class ByteStream extends OutputStream { + private byte[] buf; + private int count; + + public ByteStream() { this(32); } + + public ByteStream(int size) { + buf = new byte[size]; + count = 0; + } + + public int getPos() { return count; } + public int size() { return count; } + + public void writeBlank(int len) { + enlarge(len); + count += len; + } + + public void write(byte[] data) { + write(data, 0, data.length); + } + + public void write(byte[] data, int off, int len) { + enlarge(len); + System.arraycopy(data, off, buf, count, len); + count += len; + } + + public void write(int b) { + enlarge(1); + int oldCount = count; + buf[oldCount] = (byte)b; + count = oldCount + 1; + } + + public void writeShort(int s) { + enlarge(2); + int oldCount = count; + buf[oldCount] = (byte)(s >>> 8); + buf[oldCount + 1] = (byte)s; + count = oldCount + 2; + } + + public void writeInt(int i) { + enlarge(4); + int oldCount = count; + buf[oldCount] = (byte)(i >>> 24); + buf[oldCount + 1] = (byte)(i >>> 16); + buf[oldCount + 2] = (byte)(i >>> 8); + buf[oldCount + 3] = (byte)i; + count = oldCount + 4; + } + + public void writeLong(long i) { + enlarge(8); + int oldCount = count; + buf[oldCount] = (byte)(i >>> 56); + buf[oldCount + 1] = (byte)(i >>> 48); + buf[oldCount + 2] = (byte)(i >>> 40); + buf[oldCount + 3] = (byte)(i >>> 32); + buf[oldCount + 4] = (byte)(i >>> 24); + buf[oldCount + 5] = (byte)(i >>> 16); + buf[oldCount + 6] = (byte)(i >>> 8); + buf[oldCount + 7] = (byte)i; + count = oldCount + 8; + } + + public void writeFloat(float v) { + writeInt(Float.floatToIntBits(v)); + } + + public void writeDouble(double v) { + writeLong(Double.doubleToLongBits(v)); + } + + public void writeUTF(String s) { + int sLen = s.length(); + int pos = count; + enlarge(sLen + 2); + + byte[] buffer = buf; + buffer[pos++] = (byte)(sLen >>> 8); + buffer[pos++] = (byte)sLen; + for (int i = 0; i < sLen; ++i) { + char c = s.charAt(i); + if (0x01 <= c && c <= 0x7f) + buffer[pos++] = (byte)c; + else { + writeUTF2(s, sLen, i); + return; + } + } + + count = pos; + } + + private void writeUTF2(String s, int sLen, int offset) { + int size = sLen; + for (int i = offset; i < sLen; i++) { + int c = s.charAt(i); + if (c > 0x7ff) + size += 2; // 3 bytes code + else if (c == 0 || c > 0x7f) + ++size; // 2 bytes code + } + + if (size > 65535) + throw new RuntimeException( + "encoded string too long: " + sLen + size + " bytes"); + + enlarge(size + 2); + int pos = count; + byte[] buffer = buf; + buffer[pos] = (byte)(size >>> 8); + buffer[pos + 1] = (byte)size; + pos += 2 + offset; + for (int j = offset; j < sLen; ++j) { + int c = s.charAt(j); + if (0x01 <= c && c <= 0x7f) + buffer[pos++] = (byte) c; + else if (c > 0x07ff) { + buffer[pos] = (byte)(0xe0 | ((c >> 12) & 0x0f)); + buffer[pos + 1] = (byte)(0x80 | ((c >> 6) & 0x3f)); + buffer[pos + 2] = (byte)(0x80 | (c & 0x3f)); + pos += 3; + } + else { + buffer[pos] = (byte)(0xc0 | ((c >> 6) & 0x1f)); + buffer[pos + 1] = (byte)(0x80 | (c & 0x3f)); + pos += 2; + } + } + + count = pos; + } + + public void write(int pos, int value) { + buf[pos] = (byte)value; + } + + public void writeShort(int pos, int value) { + buf[pos] = (byte)(value >>> 8); + buf[pos + 1] = (byte)value; + } + + public void writeInt(int pos, int value) { + buf[pos] = (byte)(value >>> 24); + buf[pos + 1] = (byte)(value >>> 16); + buf[pos + 2] = (byte)(value >>> 8); + buf[pos + 3] = (byte)value; + } + + public byte[] toByteArray() { + byte[] buf2 = new byte[count]; + System.arraycopy(buf, 0, buf2, 0, count); + return buf2; + } + + public void writeTo(OutputStream out) throws IOException { + out.write(buf, 0, count); + } + + public void enlarge(int delta) { + int newCount = count + delta; + if (newCount > buf.length) { + int newLen = buf.length << 1; + byte[] newBuf = new byte[newLen > newCount ? newLen : newCount]; + System.arraycopy(buf, 0, newBuf, 0, count); + buf = newBuf; + } + } +} diff --git a/src/main/javassist/bytecode/Bytecode.java b/src/main/javassist/bytecode/Bytecode.java new file mode 100644 index 0000000..92fd1f0 --- /dev/null +++ b/src/main/javassist/bytecode/Bytecode.java @@ -0,0 +1,1410 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.bytecode; + +import javassist.CtClass; +import javassist.CtPrimitiveType; + +class ByteVector implements Cloneable { + private byte[] buffer; + private int size; + + public ByteVector() { + buffer = new byte[64]; + size = 0; + } + + public Object clone() throws CloneNotSupportedException { + ByteVector bv = (ByteVector)super.clone(); + bv.buffer = (byte[])buffer.clone(); + return bv; + } + + public final int getSize() { return size; } + + public final byte[] copy() { + byte[] b = new byte[size]; + System.arraycopy(buffer, 0, b, 0, size); + return b; + } + + public int read(int offset) { + if (offset < 0 || size <= offset) + throw new ArrayIndexOutOfBoundsException(offset); + + return buffer[offset]; + } + + public void write(int offset, int value) { + if (offset < 0 || size <= offset) + throw new ArrayIndexOutOfBoundsException(offset); + + buffer[offset] = (byte)value; + } + + public void add(int code) { + addGap(1); + buffer[size - 1] = (byte)code; + } + + public void add(int b1, int b2) { + addGap(2); + buffer[size - 2] = (byte)b1; + buffer[size - 1] = (byte)b2; + } + + public void add(int b1, int b2, int b3, int b4) { + addGap(4); + buffer[size - 4] = (byte)b1; + buffer[size - 3] = (byte)b2; + buffer[size - 2] = (byte)b3; + buffer[size - 1] = (byte)b4; + } + + public void addGap(int length) { + if (size + length > buffer.length) { + int newSize = size << 1; + if (newSize < size + length) + newSize = size + length; + + byte[] newBuf = new byte[newSize]; + System.arraycopy(buffer, 0, newBuf, 0, size); + buffer = newBuf; + } + + size += length; + } +} + +/** + * A utility class for producing a bytecode sequence. + * + * <p>A <code>Bytecode</code> object is an unbounded array + * containing bytecode. For example, + * + * <ul><pre>ConstPool cp = ...; // constant pool table + * Bytecode b = new Bytecode(cp, 1, 0); + * b.addIconst(3); + * b.addReturn(CtClass.intType); + * CodeAttribute ca = b.toCodeAttribute();</ul></pre> + * + * <p>This program produces a Code attribute including a bytecode + * sequence: + * + * <ul><pre>iconst_3 + * ireturn</pre></ul> + * + * @see ConstPool + * @see CodeAttribute + */ +public class Bytecode extends ByteVector implements Cloneable, Opcode { + /** + * Represents the <code>CtClass</code> file using the + * constant pool table given to this <code>Bytecode</code> object. + */ + public static final CtClass THIS = ConstPool.THIS; + + ConstPool constPool; + int maxStack, maxLocals; + ExceptionTable tryblocks; + private int stackDepth; + + /** + * Constructs a <code>Bytecode</code> object with an empty bytecode + * sequence. + * + * <p>The parameters <code>stacksize</code> and <code>localvars</code> + * specify initial values + * of <code>max_stack</code> and <code>max_locals</code>. + * They can be changed later. + * + * @param cp constant pool table. + * @param stacksize <code>max_stack</code>. + * @param localvars <code>max_locals</code>. + */ + public Bytecode(ConstPool cp, int stacksize, int localvars) { + constPool = cp; + maxStack = stacksize; + maxLocals = localvars; + tryblocks = new ExceptionTable(cp); + stackDepth = 0; + } + + /** + * Constructs a <code>Bytecode</code> object with an empty bytecode + * sequence. The initial values of <code>max_stack</code> and + * <code>max_locals</code> are zero. + * + * @param cp constant pool table. + * @see Bytecode#setMaxStack(int) + * @see Bytecode#setMaxLocals(int) + */ + public Bytecode(ConstPool cp) { + this(cp, 0, 0); + } + + /** + * Creates and returns a copy of this object. + * The constant pool object is shared between this object + * and the cloned object. + */ + public Object clone() { + try { + Bytecode bc = (Bytecode)super.clone(); + bc.tryblocks = (ExceptionTable)tryblocks.clone(); + return bc; + } + catch (CloneNotSupportedException cnse) { + throw new RuntimeException(cnse); + } + } + + /** + * Gets a constant pool table. + */ + public ConstPool getConstPool() { return constPool; } + + /** + * Returns <code>exception_table</code>. + */ + public ExceptionTable getExceptionTable() { return tryblocks; } + + /** + * Converts to a <code>CodeAttribute</code>. + */ + public CodeAttribute toCodeAttribute() { + return new CodeAttribute(constPool, maxStack, maxLocals, + get(), tryblocks); + } + + /** + * Returns the length of the bytecode sequence. + */ + public int length() { + return getSize(); + } + + /** + * Returns the produced bytecode sequence. + */ + public byte[] get() { + return copy(); + } + + /** + * Gets <code>max_stack</code>. + */ + public int getMaxStack() { return maxStack; } + + /** + * Sets <code>max_stack</code>. + * + * <p>This value may be automatically updated when an instruction + * is appended. A <code>Bytecode</code> object maintains the current + * stack depth whenever an instruction is added + * by <code>addOpcode()</code>. For example, if DUP is appended, + * the current stack depth is increased by one. If the new stack + * depth is more than <code>max_stack</code>, then it is assigned + * to <code>max_stack</code>. However, if branch instructions are + * appended, the current stack depth may not be correctly maintained. + * + * @see #addOpcode(int) + */ + public void setMaxStack(int size) { + maxStack = size; + } + + /** + * Gets <code>max_locals</code>. + */ + public int getMaxLocals() { return maxLocals; } + + /** + * Sets <code>max_locals</code>. + */ + public void setMaxLocals(int size) { + maxLocals = size; + } + + /** + * Sets <code>max_locals</code>. + * + * <p>This computes the number of local variables + * used to pass method parameters and sets <code>max_locals</code> + * to that number plus <code>locals</code>. + * + * @param isStatic true if <code>params</code> must be + * interpreted as parameters to a static method. + * @param params parameter types. + * @param locals the number of local variables excluding + * ones used to pass parameters. + */ + public void setMaxLocals(boolean isStatic, CtClass[] params, + int locals) { + if (!isStatic) + ++locals; + + if (params != null) { + CtClass doubleType = CtClass.doubleType; + CtClass longType = CtClass.longType; + int n = params.length; + for (int i = 0; i < n; ++i) { + CtClass type = params[i]; + if (type == doubleType || type == longType) + locals += 2; + else + ++locals; + } + } + + maxLocals = locals; + } + + /** + * Increments <code>max_locals</code>. + */ + public void incMaxLocals(int diff) { + maxLocals += diff; + } + + /** + * Adds a new entry of <code>exception_table</code>. + */ + public void addExceptionHandler(int start, int end, + int handler, CtClass type) { + addExceptionHandler(start, end, handler, + constPool.addClassInfo(type)); + } + + /** + * Adds a new entry of <code>exception_table</code>. + * + * @param type the fully-qualified name of a throwable class. + */ + public void addExceptionHandler(int start, int end, + int handler, String type) { + addExceptionHandler(start, end, handler, + constPool.addClassInfo(type)); + } + + /** + * Adds a new entry of <code>exception_table</code>. + */ + public void addExceptionHandler(int start, int end, + int handler, int type) { + tryblocks.add(start, end, handler, type); + } + + /** + * Returns the length of bytecode sequence + * that have been added so far. + */ + public int currentPc() { + return getSize(); + } + + /** + * Reads a signed 8bit value at the offset from the beginning of the + * bytecode sequence. + * + * @throws ArrayIndexOutOfBoundsException if offset is invalid. + */ + public int read(int offset) { + return super.read(offset); + } + + /** + * Reads a signed 16bit value at the offset from the beginning of the + * bytecode sequence. + */ + public int read16bit(int offset) { + int v1 = read(offset); + int v2 = read(offset + 1); + return (v1 << 8) + (v2 & 0xff); + } + + /** + * Reads a signed 32bit value at the offset from the beginning of the + * bytecode sequence. + */ + public int read32bit(int offset) { + int v1 = read16bit(offset); + int v2 = read16bit(offset + 2); + return (v1 << 16) + (v2 & 0xffff); + } + + /** + * Writes an 8bit value at the offset from the beginning of the + * bytecode sequence. + * + * @throws ArrayIndexOutOfBoundsException if offset is invalid. + */ + public void write(int offset, int value) { + super.write(offset, value); + } + + /** + * Writes an 16bit value at the offset from the beginning of the + * bytecode sequence. + */ + public void write16bit(int offset, int value) { + write(offset, value >> 8); + write(offset + 1, value); + } + + /** + * Writes an 32bit value at the offset from the beginning of the + * bytecode sequence. + */ + public void write32bit(int offset, int value) { + write16bit(offset, value >> 16); + write16bit(offset + 2, value); + } + + /** + * Appends an 8bit value to the end of the bytecode sequence. + */ + public void add(int code) { + super.add(code); + } + + /** + * Appends a 32bit value to the end of the bytecode sequence. + */ + public void add32bit(int value) { + add(value >> 24, value >> 16, value >> 8, value); + } + + /** + * Appends the length-byte gap to the end of the bytecode sequence. + * + * @param length the gap length in byte. + */ + public void addGap(int length) { + super.addGap(length); + } + + /** + * Appends an 8bit opcode to the end of the bytecode sequence. + * The current stack depth is updated. + * <code>max_stack</code> is updated if the current stack depth + * is the deepest so far. + * + * <p>Note: some instructions such as INVOKEVIRTUAL does not + * update the current stack depth since the increment depends + * on the method signature. + * <code>growStack()</code> must be explicitly called. + */ + public void addOpcode(int code) { + add(code); + growStack(STACK_GROW[code]); + } + + /** + * Increases the current stack depth. + * It also updates <code>max_stack</code> if the current stack depth + * is the deepest so far. + * + * @param diff the number added to the current stack depth. + */ + public void growStack(int diff) { + setStackDepth(stackDepth + diff); + } + + /** + * Returns the current stack depth. + */ + public int getStackDepth() { return stackDepth; } + + /** + * Sets the current stack depth. + * It also updates <code>max_stack</code> if the current stack depth + * is the deepest so far. + * + * @param depth new value. + */ + public void setStackDepth(int depth) { + stackDepth = depth; + if (stackDepth > maxStack) + maxStack = stackDepth; + } + + /** + * Appends a 16bit value to the end of the bytecode sequence. + * It never changes the current stack depth. + */ + public void addIndex(int index) { + add(index >> 8, index); + } + + /** + * Appends ALOAD or (WIDE) ALOAD_<n> + * + * @param n an index into the local variable array. + */ + public void addAload(int n) { + if (n < 4) + addOpcode(42 + n); // aload_<n> + else if (n < 0x100) { + addOpcode(ALOAD); // aload + add(n); + } + else { + addOpcode(WIDE); + addOpcode(ALOAD); + addIndex(n); + } + } + + /** + * Appends ASTORE or (WIDE) ASTORE_<n> + * + * @param n an index into the local variable array. + */ + public void addAstore(int n) { + if (n < 4) + addOpcode(75 + n); // astore_<n> + else if (n < 0x100) { + addOpcode(ASTORE); // astore + add(n); + } + else { + addOpcode(WIDE); + addOpcode(ASTORE); + addIndex(n); + } + } + + /** + * Appends ICONST or ICONST_<n> + * + * @param n the pushed integer constant. + */ + public void addIconst(int n) { + if (n < 6 && -2 < n) + addOpcode(3 + n); // iconst_<i> -1..5 + else if (n <= 127 && -128 <= n) { + addOpcode(16); // bipush + add(n); + } + else if (n <= 32767 && -32768 <= n) { + addOpcode(17); // sipush + add(n >> 8); + add(n); + } + else + addLdc(constPool.addIntegerInfo(n)); + } + + /** + * Appends an instruction for pushing zero or null on the stack. + * If the type is void, this method does not append any instruction. + * + * @param type the type of the zero value (or null). + */ + public void addConstZero(CtClass type) { + if (type.isPrimitive()) { + if (type == CtClass.longType) + addOpcode(LCONST_0); + else if (type == CtClass.floatType) + addOpcode(FCONST_0); + else if (type == CtClass.doubleType) + addOpcode(DCONST_0); + else if (type == CtClass.voidType) + throw new RuntimeException("void type?"); + else + addOpcode(ICONST_0); + } + else + addOpcode(ACONST_NULL); + } + + /** + * Appends ILOAD or (WIDE) ILOAD_<n> + * + * @param n an index into the local variable array. + */ + public void addIload(int n) { + if (n < 4) + addOpcode(26 + n); // iload_<n> + else if (n < 0x100) { + addOpcode(ILOAD); // iload + add(n); + } + else { + addOpcode(WIDE); + addOpcode(ILOAD); + addIndex(n); + } + } + + /** + * Appends ISTORE or (WIDE) ISTORE_<n> + * + * @param n an index into the local variable array. + */ + public void addIstore(int n) { + if (n < 4) + addOpcode(59 + n); // istore_<n> + else if (n < 0x100) { + addOpcode(ISTORE); // istore + add(n); + } + else { + addOpcode(WIDE); + addOpcode(ISTORE); + addIndex(n); + } + } + + /** + * Appends LCONST or LCONST_<n> + * + * @param n the pushed long integer constant. + */ + public void addLconst(long n) { + if (n == 0 || n == 1) + addOpcode(9 + (int)n); // lconst_<n> + else + addLdc2w(n); + } + + /** + * Appends LLOAD or (WIDE) LLOAD_<n> + * + * @param n an index into the local variable array. + */ + public void addLload(int n) { + if (n < 4) + addOpcode(30 + n); // lload_<n> + else if (n < 0x100) { + addOpcode(LLOAD); // lload + add(n); + } + else { + addOpcode(WIDE); + addOpcode(LLOAD); + addIndex(n); + } + } + + /** + * Appends LSTORE or LSTORE_<n> + * + * @param n an index into the local variable array. + */ + public void addLstore(int n) { + if (n < 4) + addOpcode(63 + n); // lstore_<n> + else if (n < 0x100) { + addOpcode(LSTORE); // lstore + add(n); + } + else { + addOpcode(WIDE); + addOpcode(LSTORE); + addIndex(n); + } + } + + /** + * Appends DCONST or DCONST_<n> + * + * @param d the pushed double constant. + */ + public void addDconst(double d) { + if (d == 0.0 || d == 1.0) + addOpcode(14 + (int)d); // dconst_<n> + else + addLdc2w(d); + } + + /** + * Appends DLOAD or (WIDE) DLOAD_<n> + * + * @param n an index into the local variable array. + */ + public void addDload(int n) { + if (n < 4) + addOpcode(38 + n); // dload_<n> + else if (n < 0x100) { + addOpcode(DLOAD); // dload + add(n); + } + else { + addOpcode(WIDE); + addOpcode(DLOAD); + addIndex(n); + } + } + + /** + * Appends DSTORE or (WIDE) DSTORE_<n> + * + * @param n an index into the local variable array. + */ + public void addDstore(int n) { + if (n < 4) + addOpcode(71 + n); // dstore_<n> + else if (n < 0x100) { + addOpcode(DSTORE); // dstore + add(n); + } + else { + addOpcode(WIDE); + addOpcode(DSTORE); + addIndex(n); + } + } + + /** + * Appends FCONST or FCONST_<n> + * + * @param f the pushed float constant. + */ + public void addFconst(float f) { + if (f == 0.0f || f == 1.0f || f == 2.0f) + addOpcode(11 + (int)f); // fconst_<n> + else + addLdc(constPool.addFloatInfo(f)); + } + + /** + * Appends FLOAD or (WIDE) FLOAD_<n> + * + * @param n an index into the local variable array. + */ + public void addFload(int n) { + if (n < 4) + addOpcode(34 + n); // fload_<n> + else if (n < 0x100) { + addOpcode(FLOAD); // fload + add(n); + } + else { + addOpcode(WIDE); + addOpcode(FLOAD); + addIndex(n); + } + } + + /** + * Appends FSTORE or FSTORE_<n> + * + * @param n an index into the local variable array. + */ + public void addFstore(int n) { + if (n < 4) + addOpcode(67 + n); // fstore_<n> + else if (n < 0x100) { + addOpcode(FSTORE); // fstore + add(n); + } + else { + addOpcode(WIDE); + addOpcode(FSTORE); + addIndex(n); + } + } + + /** + * Appends an instruction for loading a value from the + * local variable at the index <code>n</code>. + * + * @param n the index. + * @param type the type of the loaded value. + * @return the size of the value (1 or 2 word). + */ + public int addLoad(int n, CtClass type) { + if (type.isPrimitive()) { + if (type == CtClass.booleanType || type == CtClass.charType + || type == CtClass.byteType || type == CtClass.shortType + || type == CtClass.intType) + addIload(n); + else if (type == CtClass.longType) { + addLload(n); + return 2; + } + else if(type == CtClass.floatType) + addFload(n); + else if(type == CtClass.doubleType) { + addDload(n); + return 2; + } + else + throw new RuntimeException("void type?"); + } + else + addAload(n); + + return 1; + } + + /** + * Appends an instruction for storing a value into the + * local variable at the index <code>n</code>. + * + * @param n the index. + * @param type the type of the stored value. + * @return 2 if the type is long or double. Otherwise 1. + */ + public int addStore(int n, CtClass type) { + if (type.isPrimitive()) { + if (type == CtClass.booleanType || type == CtClass.charType + || type == CtClass.byteType || type == CtClass.shortType + || type == CtClass.intType) + addIstore(n); + else if (type == CtClass.longType) { + addLstore(n); + return 2; + } + else if (type == CtClass.floatType) + addFstore(n); + else if (type == CtClass.doubleType) { + addDstore(n); + return 2; + } + else + throw new RuntimeException("void type?"); + } + else + addAstore(n); + + return 1; + } + + /** + * Appends instructions for loading all the parameters onto the + * operand stack. + * + * @param offset the index of the first parameter. It is 0 + * if the method is static. Otherwise, it is 1. + */ + public int addLoadParameters(CtClass[] params, int offset) { + int stacksize = 0; + if (params != null) { + int n = params.length; + for (int i = 0; i < n; ++i) + stacksize += addLoad(stacksize + offset, params[i]); + } + + return stacksize; + } + + /** + * Appends CHECKCAST. + * + * @param c the type. + */ + public void addCheckcast(CtClass c) { + addOpcode(CHECKCAST); + addIndex(constPool.addClassInfo(c)); + } + + /** + * Appends CHECKCAST. + * + * @param classname a fully-qualified class name. + */ + public void addCheckcast(String classname) { + addOpcode(CHECKCAST); + addIndex(constPool.addClassInfo(classname)); + } + + /** + * Appends INSTANCEOF. + * + * @param classname the class name. + */ + public void addInstanceof(String classname) { + addOpcode(INSTANCEOF); + addIndex(constPool.addClassInfo(classname)); + } + + /** + * Appends GETFIELD. + * + * @param c the class. + * @param name the field name. + * @param type the descriptor of the field type. + * + * @see Descriptor#of(CtClass) + */ + public void addGetfield(CtClass c, String name, String type) { + add(GETFIELD); + int ci = constPool.addClassInfo(c); + addIndex(constPool.addFieldrefInfo(ci, name, type)); + growStack(Descriptor.dataSize(type) - 1); + } + + /** + * Appends GETFIELD. + * + * @param c the fully-qualified class name. + * @param name the field name. + * @param type the descriptor of the field type. + * + * @see Descriptor#of(CtClass) + */ + public void addGetfield(String c, String name, String type) { + add(GETFIELD); + int ci = constPool.addClassInfo(c); + addIndex(constPool.addFieldrefInfo(ci, name, type)); + growStack(Descriptor.dataSize(type) - 1); + } + + /** + * Appends GETSTATIC. + * + * @param c the class + * @param name the field name + * @param type the descriptor of the field type. + * + * @see Descriptor#of(CtClass) + */ + public void addGetstatic(CtClass c, String name, String type) { + add(GETSTATIC); + int ci = constPool.addClassInfo(c); + addIndex(constPool.addFieldrefInfo(ci, name, type)); + growStack(Descriptor.dataSize(type)); + } + + /** + * Appends GETSTATIC. + * + * @param c the fully-qualified class name + * @param name the field name + * @param type the descriptor of the field type. + * + * @see Descriptor#of(CtClass) + */ + public void addGetstatic(String c, String name, String type) { + add(GETSTATIC); + int ci = constPool.addClassInfo(c); + addIndex(constPool.addFieldrefInfo(ci, name, type)); + growStack(Descriptor.dataSize(type)); + } + + /** + * Appends INVOKESPECIAL. + * + * @param clazz the target class. + * @param name the method name. + * @param returnType the return type. + * @param paramTypes the parameter types. + */ + public void addInvokespecial(CtClass clazz, String name, + CtClass returnType, CtClass[] paramTypes) { + String desc = Descriptor.ofMethod(returnType, paramTypes); + addInvokespecial(clazz, name, desc); + } + + /** + * Appends INVOKESPECIAL. + * + * @param clazz the target class. + * @param name the method name + * @param desc the descriptor of the method signature. + * + * @see Descriptor#ofMethod(CtClass,CtClass[]) + * @see Descriptor#ofConstructor(CtClass[]) + */ + public void addInvokespecial(CtClass clazz, String name, String desc) { + addInvokespecial(constPool.addClassInfo(clazz), name, desc); + } + + /** + * Appends INVOKESPECIAL. + * + * @param clazz the fully-qualified class name. + * @param name the method name + * @param desc the descriptor of the method signature. + * + * @see Descriptor#ofMethod(CtClass,CtClass[]) + * @see Descriptor#ofConstructor(CtClass[]) + */ + public void addInvokespecial(String clazz, String name, String desc) { + addInvokespecial(constPool.addClassInfo(clazz), name, desc); + } + + /** + * Appends INVOKESPECIAL. + * + * @param clazz the index of <code>CONSTANT_Class_info</code> + * structure. + * @param name the method name + * @param desc the descriptor of the method signature. + * + * @see Descriptor#ofMethod(CtClass,CtClass[]) + * @see Descriptor#ofConstructor(CtClass[]) + */ + public void addInvokespecial(int clazz, String name, String desc) { + add(INVOKESPECIAL); + addIndex(constPool.addMethodrefInfo(clazz, name, desc)); + growStack(Descriptor.dataSize(desc) - 1); + } + + /** + * Appends INVOKESTATIC. + * + * @param clazz the target class. + * @param name the method name + * @param returnType the return type. + * @param paramTypes the parameter types. + */ + public void addInvokestatic(CtClass clazz, String name, + CtClass returnType, CtClass[] paramTypes) { + String desc = Descriptor.ofMethod(returnType, paramTypes); + addInvokestatic(clazz, name, desc); + } + + /** + * Appends INVOKESTATIC. + * + * @param clazz the target class. + * @param name the method name + * @param desc the descriptor of the method signature. + * + * @see Descriptor#ofMethod(CtClass,CtClass[]) + */ + public void addInvokestatic(CtClass clazz, String name, String desc) { + addInvokestatic(constPool.addClassInfo(clazz), name, desc); + } + + /** + * Appends INVOKESTATIC. + * + * @param classname the fully-qualified class name. + * @param name the method name + * @param desc the descriptor of the method signature. + * + * @see Descriptor#ofMethod(CtClass,CtClass[]) + */ + public void addInvokestatic(String classname, String name, String desc) { + addInvokestatic(constPool.addClassInfo(classname), name, desc); + } + + /** + * Appends INVOKESTATIC. + * + * @param clazz the index of <code>CONSTANT_Class_info</code> + * structure. + * @param name the method name + * @param desc the descriptor of the method signature. + * + * @see Descriptor#ofMethod(CtClass,CtClass[]) + */ + public void addInvokestatic(int clazz, String name, String desc) { + add(INVOKESTATIC); + addIndex(constPool.addMethodrefInfo(clazz, name, desc)); + growStack(Descriptor.dataSize(desc)); + } + + /** + * Appends INVOKEVIRTUAL. + * + * <p>The specified method must not be an inherited method. + * It must be directly declared in the class specified + * in <code>clazz</code>. + * + * @param clazz the target class. + * @param name the method name + * @param returnType the return type. + * @param paramTypes the parameter types. + */ + public void addInvokevirtual(CtClass clazz, String name, + CtClass returnType, CtClass[] paramTypes) { + String desc = Descriptor.ofMethod(returnType, paramTypes); + addInvokevirtual(clazz, name, desc); + } + + /** + * Appends INVOKEVIRTUAL. + * + * <p>The specified method must not be an inherited method. + * It must be directly declared in the class specified + * in <code>clazz</code>. + * + * @param clazz the target class. + * @param name the method name + * @param desc the descriptor of the method signature. + * + * @see Descriptor#ofMethod(CtClass,CtClass[]) + */ + public void addInvokevirtual(CtClass clazz, String name, String desc) { + addInvokevirtual(constPool.addClassInfo(clazz), name, desc); + } + + /** + * Appends INVOKEVIRTUAL. + * + * <p>The specified method must not be an inherited method. + * It must be directly declared in the class specified + * in <code>classname</code>. + * + * @param classname the fully-qualified class name. + * @param name the method name + * @param desc the descriptor of the method signature. + * + * @see Descriptor#ofMethod(CtClass,CtClass[]) + */ + public void addInvokevirtual(String classname, String name, String desc) { + addInvokevirtual(constPool.addClassInfo(classname), name, desc); + } + + /** + * Appends INVOKEVIRTUAL. + * + * <p>The specified method must not be an inherited method. + * It must be directly declared in the class specified + * by <code>clazz</code>. + * + * @param clazz the index of <code>CONSTANT_Class_info</code> + * structure. + * @param name the method name + * @param desc the descriptor of the method signature. + * + * @see Descriptor#ofMethod(CtClass,CtClass[]) + */ + public void addInvokevirtual(int clazz, String name, String desc) { + add(INVOKEVIRTUAL); + addIndex(constPool.addMethodrefInfo(clazz, name, desc)); + growStack(Descriptor.dataSize(desc) - 1); + } + + /** + * Appends INVOKEINTERFACE. + * + * @param clazz the target class. + * @param name the method name + * @param returnType the return type. + * @param paramTypes the parameter types. + * @param count the count operand of the instruction. + */ + public void addInvokeinterface(CtClass clazz, String name, + CtClass returnType, CtClass[] paramTypes, + int count) { + String desc = Descriptor.ofMethod(returnType, paramTypes); + addInvokeinterface(clazz, name, desc, count); + } + + /** + * Appends INVOKEINTERFACE. + * + * @param clazz the target class. + * @param name the method name + * @param desc the descriptor of the method signature. + * @param count the count operand of the instruction. + * + * @see Descriptor#ofMethod(CtClass,CtClass[]) + */ + public void addInvokeinterface(CtClass clazz, String name, + String desc, int count) { + addInvokeinterface(constPool.addClassInfo(clazz), name, desc, + count); + } + + /** + * Appends INVOKEINTERFACE. + * + * @param classname the fully-qualified class name. + * @param name the method name + * @param desc the descriptor of the method signature. + * @param count the count operand of the instruction. + * + * @see Descriptor#ofMethod(CtClass,CtClass[]) + */ + public void addInvokeinterface(String classname, String name, + String desc, int count) { + addInvokeinterface(constPool.addClassInfo(classname), name, desc, + count); + } + + /** + * Appends INVOKEINTERFACE. + * + * @param clazz the index of <code>CONSTANT_Class_info</code> + * structure. + * @param name the method name + * @param desc the descriptor of the method signature. + * @param count the count operand of the instruction. + * + * @see Descriptor#ofMethod(CtClass,CtClass[]) + */ + public void addInvokeinterface(int clazz, String name, + String desc, int count) { + add(INVOKEINTERFACE); + addIndex(constPool.addInterfaceMethodrefInfo(clazz, name, desc)); + add(count); + add(0); + growStack(Descriptor.dataSize(desc) - 1); + } + + /** + * Appends LDC or LDC_W. The pushed item is a <code>String</code> + * object. + * + * @param s the character string pushed by LDC or LDC_W. + */ + public void addLdc(String s) { + addLdc(constPool.addStringInfo(s)); + } + + /** + * Appends LDC or LDC_W. + * + * @param i index into the constant pool. + */ + public void addLdc(int i) { + if (i > 0xFF) { + addOpcode(LDC_W); + addIndex(i); + } + else { + addOpcode(LDC); + add(i); + } + } + + /** + * Appends LDC2_W. The pushed item is a long value. + */ + public void addLdc2w(long l) { + addOpcode(LDC2_W); + addIndex(constPool.addLongInfo(l)); + } + + /** + * Appends LDC2_W. The pushed item is a double value. + */ + public void addLdc2w(double d) { + addOpcode(LDC2_W); + addIndex(constPool.addDoubleInfo(d)); + } + + /** + * Appends NEW. + * + * @param clazz the class of the created instance. + */ + public void addNew(CtClass clazz) { + addOpcode(NEW); + addIndex(constPool.addClassInfo(clazz)); + } + + /** + * Appends NEW. + * + * @param classname the fully-qualified class name. + */ + public void addNew(String classname) { + addOpcode(NEW); + addIndex(constPool.addClassInfo(classname)); + } + + /** + * Appends ANEWARRAY. + * + * @param classname the qualified class name of the element type. + */ + public void addAnewarray(String classname) { + addOpcode(ANEWARRAY); + addIndex(constPool.addClassInfo(classname)); + } + + /** + * Appends ICONST and ANEWARRAY. + * + * @param clazz the elememnt type. + * @param length the array length. + */ + public void addAnewarray(CtClass clazz, int length) { + addIconst(length); + addOpcode(ANEWARRAY); + addIndex(constPool.addClassInfo(clazz)); + } + + /** + * Appends NEWARRAY for primitive types. + * + * @param atype <code>T_BOOLEAN</code>, <code>T_CHAR</code>, ... + * @see Opcode + */ + public void addNewarray(int atype, int length) { + addIconst(length); + addOpcode(NEWARRAY); + add(atype); + } + + /** + * Appends MULTINEWARRAY. + * + * @param clazz the array type. + * @param dimensions the sizes of all dimensions. + * @return the length of <code>dimensions</code>. + */ + public int addMultiNewarray(CtClass clazz, int[] dimensions) { + int len = dimensions.length; + for (int i = 0; i < len; ++i) + addIconst(dimensions[i]); + + growStack(len); + return addMultiNewarray(clazz, len); + } + + /** + * Appends MULTINEWARRAY. The size of every dimension must have been + * already pushed on the stack. + * + * @param clazz the array type. + * @param dim the number of the dimensions. + * @return the value of <code>dim</code>. + */ + public int addMultiNewarray(CtClass clazz, int dim) { + add(MULTIANEWARRAY); + addIndex(constPool.addClassInfo(clazz)); + add(dim); + growStack(1 - dim); + return dim; + } + + /** + * Appends MULTINEWARRAY. + * + * @param desc the type descriptor of the created array. + * @param dim dimensions. + * @return the value of <code>dim</code>. + */ + public int addMultiNewarray(String desc, int dim) { + add(MULTIANEWARRAY); + addIndex(constPool.addClassInfo(desc)); + add(dim); + growStack(1 - dim); + return dim; + } + + /** + * Appends PUTFIELD. + * + * @param c the target class. + * @param name the field name. + * @param desc the descriptor of the field type. + */ + public void addPutfield(CtClass c, String name, String desc) { + addPutfield0(c, null, name, desc); + } + + /** + * Appends PUTFIELD. + * + * @param classname the fully-qualified name of the target class. + * @param name the field name. + * @param desc the descriptor of the field type. + */ + public void addPutfield(String classname, String name, String desc) { + // if classnaem is null, the target class is THIS. + addPutfield0(null, classname, name, desc); + } + + private void addPutfield0(CtClass target, String classname, + String name, String desc) { + add(PUTFIELD); + // target is null if it represents THIS. + int ci = classname == null ? constPool.addClassInfo(target) + : constPool.addClassInfo(classname); + addIndex(constPool.addFieldrefInfo(ci, name, desc)); + growStack(-1 - Descriptor.dataSize(desc)); + } + + /** + * Appends PUTSTATIC. + * + * @param c the target class. + * @param name the field name. + * @param desc the descriptor of the field type. + */ + public void addPutstatic(CtClass c, String name, String desc) { + addPutstatic0(c, null, name, desc); + } + + /** + * Appends PUTSTATIC. + * + * @param classname the fully-qualified name of the target class. + * @param fieldName the field name. + * @param desc the descriptor of the field type. + */ + public void addPutstatic(String classname, String fieldName, String desc) { + // if classname is null, the target class is THIS. + addPutstatic0(null, classname, fieldName, desc); + } + + private void addPutstatic0(CtClass target, String classname, + String fieldName, String desc) { + add(PUTSTATIC); + // target is null if it represents THIS. + int ci = classname == null ? constPool.addClassInfo(target) + : constPool.addClassInfo(classname); + addIndex(constPool.addFieldrefInfo(ci, fieldName, desc)); + growStack(-Descriptor.dataSize(desc)); + } + + /** + * Appends ARETURN, IRETURN, .., or RETURN. + * + * @param type the return type. + */ + public void addReturn(CtClass type) { + if (type == null) + addOpcode(RETURN); + else if (type.isPrimitive()) { + CtPrimitiveType ptype = (CtPrimitiveType)type; + addOpcode(ptype.getReturnOp()); + } + else + addOpcode(ARETURN); + } + + /** + * Appends RET. + * + * @param var local variable + */ + public void addRet(int var) { + if (var < 0x100) { + addOpcode(RET); + add(var); + } + else { + addOpcode(WIDE); + addOpcode(RET); + addIndex(var); + } + } + + /** + * Appends instructions for executing + * <code>java.lang.System.println(<i>message</i>)</code>. + * + * @param message printed message. + */ + public void addPrintln(String message) { + addGetstatic("java.lang.System", "err", "Ljava/io/PrintStream;"); + addLdc(message); + addInvokevirtual("java.io.PrintStream", + "println", "(Ljava/lang/String;)V"); + } +} diff --git a/src/main/javassist/bytecode/ClassFile.java b/src/main/javassist/bytecode/ClassFile.java new file mode 100644 index 0000000..d07d108 --- /dev/null +++ b/src/main/javassist/bytecode/ClassFile.java @@ -0,0 +1,889 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.bytecode; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import javassist.CannotCompileException; + +/** + * <code>ClassFile</code> represents a Java <code>.class</code> file, which + * consists of a constant pool, methods, fields, and attributes. + * + * @see javassist.CtClass#getClassFile() + */ +public final class ClassFile { + int major, minor; // version number + ConstPool constPool; + int thisClass; + int accessFlags; + int superClass; + int[] interfaces; + ArrayList fields; + ArrayList methods; + ArrayList attributes; + String thisclassname; // not JVM-internal name + String[] cachedInterfaces; + String cachedSuperclass; + + /** + * The major version number of class files + * for JDK 1.1. + */ + public static final int JAVA_1 = 45; + + /** + * The major version number of class files + * for JDK 1.2. + */ + public static final int JAVA_2 = 46; + + /** + * The major version number of class files + * for JDK 1.3. + */ + public static final int JAVA_3 = 47; + + /** + * The major version number of class files + * for JDK 1.4. + */ + public static final int JAVA_4 = 48; + + /** + * The major version number of class files + * for JDK 1.5. + */ + public static final int JAVA_5 = 49; + + /** + * The major version number of class files + * for JDK 1.6. + */ + public static final int JAVA_6 = 50; + + /** + * The major version number of class files + * for JDK 1.7. + */ + public static final int JAVA_7 = 51; + + /** + * The major version number of class files created + * from scratch. The default value is 47 (JDK 1.3) + * or 49 (JDK 1.5) if the JVM supports <code>java.lang.StringBuilder</code>. + */ + public static int MAJOR_VERSION = JAVA_3; + + static { + try { + Class.forName("java.lang.StringBuilder"); + MAJOR_VERSION = JAVA_5; + } + catch (Throwable t) {} + } + + /** + * Constructs a class file from a byte stream. + */ + public ClassFile(DataInputStream in) throws IOException { + read(in); + } + + /** + * Constructs a class file including no members. + * + * @param isInterface + * true if this is an interface. false if this is a class. + * @param classname + * a fully-qualified class name + * @param superclass + * a fully-qualified super class name + */ + public ClassFile(boolean isInterface, String classname, String superclass) { + major = MAJOR_VERSION; + minor = 0; // JDK 1.3 or later + constPool = new ConstPool(classname); + thisClass = constPool.getThisClassInfo(); + if (isInterface) + accessFlags = AccessFlag.INTERFACE | AccessFlag.ABSTRACT; + else + accessFlags = AccessFlag.SUPER; + + initSuperclass(superclass); + interfaces = null; + fields = new ArrayList(); + methods = new ArrayList(); + thisclassname = classname; + + attributes = new ArrayList(); + attributes.add(new SourceFileAttribute(constPool, + getSourcefileName(thisclassname))); + } + + private void initSuperclass(String superclass) { + if (superclass != null) { + this.superClass = constPool.addClassInfo(superclass); + cachedSuperclass = superclass; + } + else { + this.superClass = constPool.addClassInfo("java.lang.Object"); + cachedSuperclass = "java.lang.Object"; + } + } + + private static String getSourcefileName(String qname) { + int index = qname.lastIndexOf('.'); + if (index >= 0) + qname = qname.substring(index + 1); + + return qname + ".java"; + } + + /** + * Eliminates dead constant pool items. If a method or a field is removed, + * the constant pool items used by that method/field become dead items. This + * method recreates a constant pool. + */ + public void compact() { + ConstPool cp = compact0(); + ArrayList list = methods; + int n = list.size(); + for (int i = 0; i < n; ++i) { + MethodInfo minfo = (MethodInfo)list.get(i); + minfo.compact(cp); + } + + list = fields; + n = list.size(); + for (int i = 0; i < n; ++i) { + FieldInfo finfo = (FieldInfo)list.get(i); + finfo.compact(cp); + } + + attributes = AttributeInfo.copyAll(attributes, cp); + constPool = cp; + } + + private ConstPool compact0() { + ConstPool cp = new ConstPool(thisclassname); + thisClass = cp.getThisClassInfo(); + String sc = getSuperclass(); + if (sc != null) + superClass = cp.addClassInfo(getSuperclass()); + + if (interfaces != null) { + int n = interfaces.length; + for (int i = 0; i < n; ++i) + interfaces[i] + = cp.addClassInfo(constPool.getClassInfo(interfaces[i])); + } + + return cp; + } + + /** + * Discards all attributes, associated with both the class file and the + * members such as a code attribute and exceptions attribute. The unused + * constant pool entries are also discarded (a new packed constant pool is + * constructed). + */ + public void prune() { + ConstPool cp = compact0(); + ArrayList newAttributes = new ArrayList(); + AttributeInfo invisibleAnnotations + = getAttribute(AnnotationsAttribute.invisibleTag); + if (invisibleAnnotations != null) { + invisibleAnnotations = invisibleAnnotations.copy(cp, null); + newAttributes.add(invisibleAnnotations); + } + + AttributeInfo visibleAnnotations + = getAttribute(AnnotationsAttribute.visibleTag); + if (visibleAnnotations != null) { + visibleAnnotations = visibleAnnotations.copy(cp, null); + newAttributes.add(visibleAnnotations); + } + + AttributeInfo signature + = getAttribute(SignatureAttribute.tag); + if (signature != null) { + signature = signature.copy(cp, null); + newAttributes.add(signature); + } + + ArrayList list = methods; + int n = list.size(); + for (int i = 0; i < n; ++i) { + MethodInfo minfo = (MethodInfo)list.get(i); + minfo.prune(cp); + } + + list = fields; + n = list.size(); + for (int i = 0; i < n; ++i) { + FieldInfo finfo = (FieldInfo)list.get(i); + finfo.prune(cp); + } + + attributes = newAttributes; + constPool = cp; + } + + /** + * Returns a constant pool table. + */ + public ConstPool getConstPool() { + return constPool; + } + + /** + * Returns true if this is an interface. + */ + public boolean isInterface() { + return (accessFlags & AccessFlag.INTERFACE) != 0; + } + + /** + * Returns true if this is a final class or interface. + */ + public boolean isFinal() { + return (accessFlags & AccessFlag.FINAL) != 0; + } + + /** + * Returns true if this is an abstract class or an interface. + */ + public boolean isAbstract() { + return (accessFlags & AccessFlag.ABSTRACT) != 0; + } + + /** + * Returns access flags. + * + * @see javassist.bytecode.AccessFlag + */ + public int getAccessFlags() { + return accessFlags; + } + + /** + * Changes access flags. + * + * @see javassist.bytecode.AccessFlag + */ + public void setAccessFlags(int acc) { + if ((acc & AccessFlag.INTERFACE) == 0) + acc |= AccessFlag.SUPER; + + accessFlags = acc; + } + + /** + * Returns access and property flags of this nested class. + * This method returns -1 if the class is not a nested class. + * + * <p>The returned value is obtained from <code>inner_class_access_flags</code> + * of the entry representing this nested class itself + * in <code>InnerClasses_attribute</code>>. + */ + public int getInnerAccessFlags() { + InnerClassesAttribute ica + = (InnerClassesAttribute)getAttribute(InnerClassesAttribute.tag); + if (ica == null) + return -1; + + String name = getName(); + int n = ica.tableLength(); + for (int i = 0; i < n; ++i) + if (name.equals(ica.innerClass(i))) + return ica.accessFlags(i); + + return -1; + } + + /** + * Returns the class name. + */ + public String getName() { + return thisclassname; + } + + /** + * Sets the class name. This method substitutes the new name for all + * occurrences of the old class name in the class file. + */ + public void setName(String name) { + renameClass(thisclassname, name); + } + + /** + * Returns the super class name. + */ + public String getSuperclass() { + if (cachedSuperclass == null) + cachedSuperclass = constPool.getClassInfo(superClass); + + return cachedSuperclass; + } + + /** + * Returns the index of the constant pool entry representing the super + * class. + */ + public int getSuperclassId() { + return superClass; + } + + /** + * Sets the super class. + * + * <p> + * The new super class should inherit from the old super class. + * This method modifies constructors so that they call constructors declared + * in the new super class. + */ + public void setSuperclass(String superclass) throws CannotCompileException { + if (superclass == null) + superclass = "java.lang.Object"; + + try { + this.superClass = constPool.addClassInfo(superclass); + ArrayList list = methods; + int n = list.size(); + for (int i = 0; i < n; ++i) { + MethodInfo minfo = (MethodInfo)list.get(i); + minfo.setSuperclass(superclass); + } + } + catch (BadBytecode e) { + throw new CannotCompileException(e); + } + cachedSuperclass = superclass; + } + + /** + * Replaces all occurrences of a class name in the class file. + * + * <p> + * If class X is substituted for class Y in the class file, X and Y must + * have the same signature. If Y provides a method m(), X must provide it + * even if X inherits m() from the super class. If this fact is not + * guaranteed, the bytecode verifier may cause an error. + * + * @param oldname + * the replaced class name + * @param newname + * the substituted class name + */ + public final void renameClass(String oldname, String newname) { + ArrayList list; + int n; + + if (oldname.equals(newname)) + return; + + if (oldname.equals(thisclassname)) + thisclassname = newname; + + oldname = Descriptor.toJvmName(oldname); + newname = Descriptor.toJvmName(newname); + constPool.renameClass(oldname, newname); + + AttributeInfo.renameClass(attributes, oldname, newname); + list = methods; + n = list.size(); + for (int i = 0; i < n; ++i) { + MethodInfo minfo = (MethodInfo)list.get(i); + String desc = minfo.getDescriptor(); + minfo.setDescriptor(Descriptor.rename(desc, oldname, newname)); + AttributeInfo.renameClass(minfo.getAttributes(), oldname, newname); + } + + list = fields; + n = list.size(); + for (int i = 0; i < n; ++i) { + FieldInfo finfo = (FieldInfo)list.get(i); + String desc = finfo.getDescriptor(); + finfo.setDescriptor(Descriptor.rename(desc, oldname, newname)); + AttributeInfo.renameClass(finfo.getAttributes(), oldname, newname); + } + } + + /** + * Replaces all occurrences of several class names in the class file. + * + * @param classnames + * specifies which class name is replaced with which new name. + * Class names must be described with the JVM-internal + * representation like <code>java/lang/Object</code>. + * @see #renameClass(String,String) + */ + public final void renameClass(Map classnames) { + String jvmNewThisName = (String)classnames.get(Descriptor + .toJvmName(thisclassname)); + if (jvmNewThisName != null) + thisclassname = Descriptor.toJavaName(jvmNewThisName); + + constPool.renameClass(classnames); + + AttributeInfo.renameClass(attributes, classnames); + ArrayList list = methods; + int n = list.size(); + for (int i = 0; i < n; ++i) { + MethodInfo minfo = (MethodInfo)list.get(i); + String desc = minfo.getDescriptor(); + minfo.setDescriptor(Descriptor.rename(desc, classnames)); + AttributeInfo.renameClass(minfo.getAttributes(), classnames); + } + + list = fields; + n = list.size(); + for (int i = 0; i < n; ++i) { + FieldInfo finfo = (FieldInfo)list.get(i); + String desc = finfo.getDescriptor(); + finfo.setDescriptor(Descriptor.rename(desc, classnames)); + AttributeInfo.renameClass(finfo.getAttributes(), classnames); + } + } + + /** + * Internal-use only. + * <code>CtClass.getRefClasses()</code> calls this method. + */ + public final void getRefClasses(Map classnames) { + constPool.renameClass(classnames); + + AttributeInfo.getRefClasses(attributes, classnames); + ArrayList list = methods; + int n = list.size(); + for (int i = 0; i < n; ++i) { + MethodInfo minfo = (MethodInfo)list.get(i); + String desc = minfo.getDescriptor(); + Descriptor.rename(desc, classnames); + AttributeInfo.getRefClasses(minfo.getAttributes(), classnames); + } + + list = fields; + n = list.size(); + for (int i = 0; i < n; ++i) { + FieldInfo finfo = (FieldInfo)list.get(i); + String desc = finfo.getDescriptor(); + Descriptor.rename(desc, classnames); + AttributeInfo.getRefClasses(finfo.getAttributes(), classnames); + } + } + + /** + * Returns the names of the interfaces implemented by the class. + * The returned array is read only. + */ + public String[] getInterfaces() { + if (cachedInterfaces != null) + return cachedInterfaces; + + String[] rtn = null; + if (interfaces == null) + rtn = new String[0]; + else { + int n = interfaces.length; + String[] list = new String[n]; + for (int i = 0; i < n; ++i) + list[i] = constPool.getClassInfo(interfaces[i]); + + rtn = list; + } + + cachedInterfaces = rtn; + return rtn; + } + + /** + * Sets the interfaces. + * + * @param nameList + * the names of the interfaces. + */ + public void setInterfaces(String[] nameList) { + cachedInterfaces = null; + if (nameList != null) { + int n = nameList.length; + interfaces = new int[n]; + for (int i = 0; i < n; ++i) + interfaces[i] = constPool.addClassInfo(nameList[i]); + } + } + + /** + * Appends an interface to the interfaces implemented by the class. + */ + public void addInterface(String name) { + cachedInterfaces = null; + int info = constPool.addClassInfo(name); + if (interfaces == null) { + interfaces = new int[1]; + interfaces[0] = info; + } + else { + int n = interfaces.length; + int[] newarray = new int[n + 1]; + System.arraycopy(interfaces, 0, newarray, 0, n); + newarray[n] = info; + interfaces = newarray; + } + } + + /** + * Returns all the fields declared in the class. + * + * @return a list of <code>FieldInfo</code>. + * @see FieldInfo + */ + public List getFields() { + return fields; + } + + /** + * Appends a field to the class. + * + * @throws DuplicateMemberException when the field is already included. + */ + public void addField(FieldInfo finfo) throws DuplicateMemberException { + testExistingField(finfo.getName(), finfo.getDescriptor()); + fields.add(finfo); + } + + /** + * Just appends a field to the class. + * It does not check field duplication. + * Use this method only when minimizing performance overheads + * is seriously required. + * + * @since 3.13 + */ + public final void addField2(FieldInfo finfo) { + fields.add(finfo); + } + + private void testExistingField(String name, String descriptor) + throws DuplicateMemberException { + ListIterator it = fields.listIterator(0); + while (it.hasNext()) { + FieldInfo minfo = (FieldInfo)it.next(); + if (minfo.getName().equals(name)) + throw new DuplicateMemberException("duplicate field: " + name); + } + } + + /** + * Returns all the methods declared in the class. + * + * @return a list of <code>MethodInfo</code>. + * @see MethodInfo + */ + public List getMethods() { + return methods; + } + + /** + * Returns the method with the specified name. If there are multiple methods + * with that name, this method returns one of them. + * + * @return null if no such method is found. + */ + public MethodInfo getMethod(String name) { + ArrayList list = methods; + int n = list.size(); + for (int i = 0; i < n; ++i) { + MethodInfo minfo = (MethodInfo)list.get(i); + if (minfo.getName().equals(name)) + return minfo; + } + + return null; + } + + /** + * Returns a static initializer (class initializer), or null if it does not + * exist. + */ + public MethodInfo getStaticInitializer() { + return getMethod(MethodInfo.nameClinit); + } + + /** + * Appends a method to the class. + * If there is a bridge method with the same name and signature, + * then the bridge method is removed before a new method is added. + * + * @throws DuplicateMemberException when the method is already included. + */ + public void addMethod(MethodInfo minfo) throws DuplicateMemberException { + testExistingMethod(minfo); + methods.add(minfo); + } + + /** + * Just appends a method to the class. + * It does not check method duplication or remove a bridge method. + * Use this method only when minimizing performance overheads + * is seriously required. + * + * @since 3.13 + */ + public final void addMethod2(MethodInfo minfo) { + methods.add(minfo); + } + + private void testExistingMethod(MethodInfo newMinfo) + throws DuplicateMemberException + { + String name = newMinfo.getName(); + String descriptor = newMinfo.getDescriptor(); + ListIterator it = methods.listIterator(0); + while (it.hasNext()) + if (isDuplicated(newMinfo, name, descriptor, (MethodInfo)it.next(), it)) + throw new DuplicateMemberException("duplicate method: " + name + + " in " + this.getName()); + } + + private static boolean isDuplicated(MethodInfo newMethod, String newName, + String newDesc, MethodInfo minfo, + ListIterator it) + { + if (!minfo.getName().equals(newName)) + return false; + + String desc = minfo.getDescriptor(); + if (!Descriptor.eqParamTypes(desc, newDesc)) + return false; + + if (desc.equals(newDesc)) { + if (notBridgeMethod(minfo)) + return true; + else { + it.remove(); + return false; + } + } + else + return notBridgeMethod(minfo) && notBridgeMethod(newMethod); + } + + /* For a bridge method, see Sec. 15.12.4.5 of JLS 3rd Ed. + */ + private static boolean notBridgeMethod(MethodInfo minfo) { + return (minfo.getAccessFlags() & AccessFlag.BRIDGE) == 0; + } + + /** + * Returns all the attributes. The returned <code>List</code> object + * is shared with this object. If you add a new attribute to the list, + * the attribute is also added to the classs file represented by this + * object. If you remove an attribute from the list, it is also removed + * from the class file. + * + * @return a list of <code>AttributeInfo</code> objects. + * @see AttributeInfo + */ + public List getAttributes() { + return attributes; + } + + /** + * Returns the attribute with the specified name. If there are multiple + * attributes with that name, this method returns either of them. It + * returns null if the specified attributed is not found. + * + * @param name attribute name + * @see #getAttributes() + */ + public AttributeInfo getAttribute(String name) { + ArrayList list = attributes; + int n = list.size(); + for (int i = 0; i < n; ++i) { + AttributeInfo ai = (AttributeInfo)list.get(i); + if (ai.getName().equals(name)) + return ai; + } + + return null; + } + + /** + * Appends an attribute. If there is already an attribute with the same + * name, the new one substitutes for it. + * + * @see #getAttributes() + */ + public void addAttribute(AttributeInfo info) { + AttributeInfo.remove(attributes, info.getName()); + attributes.add(info); + } + + /** + * Returns the source file containing this class. + * + * @return null if this information is not available. + */ + public String getSourceFile() { + SourceFileAttribute sf + = (SourceFileAttribute)getAttribute(SourceFileAttribute.tag); + if (sf == null) + return null; + else + return sf.getFileName(); + } + + private void read(DataInputStream in) throws IOException { + int i, n; + int magic = in.readInt(); + if (magic != 0xCAFEBABE) + throw new IOException("bad magic number: " + Integer.toHexString(magic)); + + minor = in.readUnsignedShort(); + major = in.readUnsignedShort(); + constPool = new ConstPool(in); + accessFlags = in.readUnsignedShort(); + thisClass = in.readUnsignedShort(); + constPool.setThisClassInfo(thisClass); + superClass = in.readUnsignedShort(); + n = in.readUnsignedShort(); + if (n == 0) + interfaces = null; + else { + interfaces = new int[n]; + for (i = 0; i < n; ++i) + interfaces[i] = in.readUnsignedShort(); + } + + ConstPool cp = constPool; + n = in.readUnsignedShort(); + fields = new ArrayList(); + for (i = 0; i < n; ++i) + addField2(new FieldInfo(cp, in)); + + n = in.readUnsignedShort(); + methods = new ArrayList(); + for (i = 0; i < n; ++i) + addMethod2(new MethodInfo(cp, in)); + + attributes = new ArrayList(); + n = in.readUnsignedShort(); + for (i = 0; i < n; ++i) + addAttribute(AttributeInfo.read(cp, in)); + + thisclassname = constPool.getClassInfo(thisClass); + } + + /** + * Writes a class file represened by this object into an output stream. + */ + public void write(DataOutputStream out) throws IOException { + int i, n; + + out.writeInt(0xCAFEBABE); // magic + out.writeShort(minor); // minor version + out.writeShort(major); // major version + constPool.write(out); // constant pool + out.writeShort(accessFlags); + out.writeShort(thisClass); + out.writeShort(superClass); + + if (interfaces == null) + n = 0; + else + n = interfaces.length; + + out.writeShort(n); + for (i = 0; i < n; ++i) + out.writeShort(interfaces[i]); + + ArrayList list = fields; + n = list.size(); + out.writeShort(n); + for (i = 0; i < n; ++i) { + FieldInfo finfo = (FieldInfo)list.get(i); + finfo.write(out); + } + + list = methods; + n = list.size(); + out.writeShort(n); + for (i = 0; i < n; ++i) { + MethodInfo minfo = (MethodInfo)list.get(i); + minfo.write(out); + } + + out.writeShort(attributes.size()); + AttributeInfo.writeAll(attributes, out); + } + + /** + * Get the Major version. + * + * @return the major version + */ + public int getMajorVersion() { + return major; + } + + /** + * Set the major version. + * + * @param major + * the major version + */ + public void setMajorVersion(int major) { + this.major = major; + } + + /** + * Get the minor version. + * + * @return the minor version + */ + public int getMinorVersion() { + return minor; + } + + /** + * Set the minor version. + * + * @param minor + * the minor version + */ + public void setMinorVersion(int minor) { + this.minor = minor; + } + + /** + * Sets the major and minor version to Java 5. + * + * If the major version is older than 49, Java 5 + * extensions such as annotations are ignored + * by the JVM. + */ + public void setVersionToJava5() { + this.major = 49; + this.minor = 0; + } +} diff --git a/src/main/javassist/bytecode/ClassFilePrinter.java b/src/main/javassist/bytecode/ClassFilePrinter.java new file mode 100644 index 0000000..08078dc --- /dev/null +++ b/src/main/javassist/bytecode/ClassFilePrinter.java @@ -0,0 +1,152 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.bytecode; + +import java.io.PrintWriter; +import javassist.Modifier; +import java.util.List; + +/** + * A utility class for priting the contents of a class file. + * It prints a constant pool table, fields, and methods in a + * human readable representation. + */ +public class ClassFilePrinter { + /** + * Prints the contents of a class file to the standard output stream. + */ + public static void print(ClassFile cf) { + print(cf, new PrintWriter(System.out, true)); + } + + /** + * Prints the contents of a class file. + */ + public static void print(ClassFile cf, PrintWriter out) { + List list; + int n; + + /* 0x0020 (SYNCHRONIZED) means ACC_SUPER if the modifiers + * are of a class. + */ + int mod + = AccessFlag.toModifier(cf.getAccessFlags() + & ~AccessFlag.SYNCHRONIZED); + out.println("major: " + cf.major + ", minor: " + cf.minor + + " modifiers: " + Integer.toHexString(cf.getAccessFlags())); + out.println(Modifier.toString(mod) + " class " + + cf.getName() + " extends " + cf.getSuperclass()); + + String[] infs = cf.getInterfaces(); + if (infs != null && infs.length > 0) { + out.print(" implements "); + out.print(infs[0]); + for (int i = 1; i < infs.length; ++i) + out.print(", " + infs[i]); + + out.println(); + } + + out.println(); + list = cf.getFields(); + n = list.size(); + for (int i = 0; i < n; ++i) { + FieldInfo finfo = (FieldInfo)list.get(i); + int acc = finfo.getAccessFlags(); + out.println(Modifier.toString(AccessFlag.toModifier(acc)) + + " " + finfo.getName() + "\t" + + finfo.getDescriptor()); + printAttributes(finfo.getAttributes(), out, 'f'); + } + + out.println(); + list = cf.getMethods(); + n = list.size(); + for (int i = 0; i < n; ++i) { + MethodInfo minfo = (MethodInfo)list.get(i); + int acc = minfo.getAccessFlags(); + out.println(Modifier.toString(AccessFlag.toModifier(acc)) + + " " + minfo.getName() + "\t" + + minfo.getDescriptor()); + printAttributes(minfo.getAttributes(), out, 'm'); + out.println(); + } + + out.println(); + printAttributes(cf.getAttributes(), out, 'c'); + } + + static void printAttributes(List list, PrintWriter out, char kind) { + if (list == null) + return; + + int n = list.size(); + for (int i = 0; i < n; ++i) { + AttributeInfo ai = (AttributeInfo)list.get(i); + if (ai instanceof CodeAttribute) { + CodeAttribute ca = (CodeAttribute)ai; + out.println("attribute: " + ai.getName() + ": " + + ai.getClass().getName()); + out.println("max stack " + ca.getMaxStack() + + ", max locals " + ca.getMaxLocals() + + ", " + ca.getExceptionTable().size() + + " catch blocks"); + out.println("<code attribute begin>"); + printAttributes(ca.getAttributes(), out, kind); + out.println("<code attribute end>"); + } + else if (ai instanceof AnnotationsAttribute) { + out.println("annnotation: " + ai.toString()); + } + else if (ai instanceof ParameterAnnotationsAttribute) { + out.println("parameter annnotations: " + ai.toString()); + } + else if (ai instanceof StackMapTable) { + out.println("<stack map table begin>"); + StackMapTable.Printer.print((StackMapTable)ai, out); + out.println("<stack map table end>"); + } + else if (ai instanceof StackMap) { + out.println("<stack map begin>"); + ((StackMap)ai).print(out); + out.println("<stack map end>"); + } + else if (ai instanceof SignatureAttribute) { + SignatureAttribute sa = (SignatureAttribute)ai; + String sig = sa.getSignature(); + out.println("signature: " + sig); + try { + String s; + if (kind == 'c') + s = SignatureAttribute.toClassSignature(sig).toString(); + else if (kind == 'm') + s = SignatureAttribute.toMethodSignature(sig).toString(); + else + s = SignatureAttribute.toFieldSignature(sig).toString(); + + out.println(" " + s); + } + catch (BadBytecode e) { + out.println(" syntax error"); + } + } + else + out.println("attribute: " + ai.getName() + + " (" + ai.get().length + " byte): " + + ai.getClass().getName()); + } + } +} diff --git a/src/main/javassist/bytecode/ClassFileWriter.java b/src/main/javassist/bytecode/ClassFileWriter.java new file mode 100644 index 0000000..bb7342a --- /dev/null +++ b/src/main/javassist/bytecode/ClassFileWriter.java @@ -0,0 +1,731 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2010 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.bytecode; + +import java.io.OutputStream; +import java.io.DataOutputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +/** + * A quick class-file writer. This is useful when a generated + * class file is simple and the code generation should be fast. + * + * <p>Example: + * + * <blockquote><pre> + * ClassFileWriter cfw = new ClassFileWriter(ClassFile.JAVA_4, 0); + * ConstPoolWriter cpw = cfw.getConstPool(); + * + * FieldWriter fw = cfw.getFieldWriter(); + * fw.add(AccessFlag.PUBLIC, "value", "I", null); + * fw.add(AccessFlag.PUBLIC, "value2", "J", null); + * + * int thisClass = cpw.addClassInfo("sample/Test"); + * int superClass = cpw.addClassInfo("java/lang/Object"); + * + * MethodWriter mw = cfw.getMethodWriter(); + * + * mw.begin(AccessFlag.PUBLIC, MethodInfo.nameInit, "()V", null, null); + * mw.add(Opcode.ALOAD_0); + * mw.add(Opcode.INVOKESPECIAL); + * int signature = cpw.addNameAndTypeInfo(MethodInfo.nameInit, "()V"); + * mw.add16(cpw.addMethodrefInfo(superClass, signature)); + * mw.add(Opcode.RETURN); + * mw.codeEnd(1, 1); + * mw.end(null, null); + * + * mw.begin(AccessFlag.PUBLIC, "one", "()I", null, null); + * mw.add(Opcode.ICONST_1); + * mw.add(Opcode.IRETURN); + * mw.codeEnd(1, 1); + * mw.end(null, null); + * + * byte[] classfile = cfw.end(AccessFlag.PUBLIC, thisClass, superClass, + * null, null); + * </pre></blockquote> + * + * <p>The code above generates the following class: + * + * <blockquote><pre> + * package sample; + * public class Test { + * public int value; + * public long value2; + * public Test() { super(); } + * public one() { return 1; } + * } + * </pre></blockquote> + * + * @since 3.13 + */ +public class ClassFileWriter { + private ByteStream output; + private ConstPoolWriter constPool; + private FieldWriter fields; + private MethodWriter methods; + int thisClass, superClass; + + /** + * Constructs a class file writer. + * + * @param major the major version ({@link ClassFile#JAVA_4}, {@link ClassFile#JAVA_5}, ...). + * @param minor the minor version (0 for JDK 1.3 and later). + */ + public ClassFileWriter(int major, int minor) { + output = new ByteStream(512); + output.writeInt(0xCAFEBABE); // magic + output.writeShort(minor); + output.writeShort(major); + constPool = new ConstPoolWriter(output); + fields = new FieldWriter(constPool); + methods = new MethodWriter(constPool); + + } + + /** + * Returns a constant pool. + */ + public ConstPoolWriter getConstPool() { return constPool; } + + /** + * Returns a filed writer. + */ + public FieldWriter getFieldWriter() { return fields; } + + /** + * Returns a method writer. + */ + public MethodWriter getMethodWriter() { return methods; } + + /** + * Ends writing and returns the contents of the class file. + * + * @param accessFlags access flags. + * @param thisClass this class. an index indicating its <code>CONSTANT_Class_info</code>. + * @param superClass super class. an index indicating its <code>CONSTANT_Class_info</code>. + * @param interfaces implemented interfaces. + * index numbers indicating their <code>ClassInfo</code>. + * It may be null. + * @param aw attributes of the class file. May be null. + * + * @see AccessFlag + */ + public byte[] end(int accessFlags, int thisClass, int superClass, + int[] interfaces, AttributeWriter aw) { + constPool.end(); + output.writeShort(accessFlags); + output.writeShort(thisClass); + output.writeShort(superClass); + if (interfaces == null) + output.writeShort(0); + else { + int n = interfaces.length; + output.writeShort(n); + for (int i = 0; i < n; i++) + output.writeShort(interfaces[i]); + } + + output.enlarge(fields.dataSize() + methods.dataSize() + 6); + try { + output.writeShort(fields.size()); + fields.write(output); + + output.writeShort(methods.size()); + methods.write(output); + } + catch (IOException e) {} + + writeAttribute(output, aw, 0); + return output.toByteArray(); + } + + /** + * Ends writing and writes the contents of the class file into the + * given output stream. + * + * @param accessFlags access flags. + * @param thisClass this class. an index indicating its <code>CONSTANT_Class_info</code>. + * @param superClass super class. an index indicating its <code>CONSTANT_Class_info</code>. + * @param interfaces implemented interfaces. + * index numbers indicating their <code>CONSTATNT_Class_info</code>. + * It may be null. + * @param aw attributes of the class file. May be null. + * + * @see AccessFlag + */ + public void end(DataOutputStream out, + int accessFlags, int thisClass, int superClass, + int[] interfaces, AttributeWriter aw) + throws IOException + { + constPool.end(); + output.writeTo(out); + out.writeShort(accessFlags); + out.writeShort(thisClass); + out.writeShort(superClass); + if (interfaces == null) + out.writeShort(0); + else { + int n = interfaces.length; + out.writeShort(n); + for (int i = 0; i < n; i++) + out.writeShort(interfaces[i]); + } + + out.writeShort(fields.size()); + fields.write(out); + + out.writeShort(methods.size()); + methods.write(out); + if (aw == null) + out.writeShort(0); + else { + out.writeShort(aw.size()); + aw.write(out); + } + } + + /** + * This writes attributes. + * + * <p>For example, the following object writes a synthetic attribute: + * + * <pre> + * ConstPoolWriter cpw = ...; + * final int tag = cpw.addUtf8Info("Synthetic"); + * AttributeWriter aw = new AttributeWriter() { + * public int size() { + * return 1; + * } + * public void write(DataOutputStream out) throws java.io.IOException { + * out.writeShort(tag); + * out.writeInt(0); + * } + * }; + * </pre> + */ + public static interface AttributeWriter { + /** + * Returns the number of attributes that this writer will + * write. + */ + public int size(); + + /** + * Writes all the contents of the attributes. The binary representation + * of the contents is an array of <code>attribute_info</code>. + */ + public void write(DataOutputStream out) throws IOException; + } + + static void writeAttribute(ByteStream bs, AttributeWriter aw, int attrCount) { + if (aw == null) { + bs.writeShort(attrCount); + return; + } + + bs.writeShort(aw.size() + attrCount); + DataOutputStream dos = new DataOutputStream(bs); + try { + aw.write(dos); + dos.flush(); + } + catch (IOException e) {} + } + + /** + * Field. + */ + public static final class FieldWriter { + protected ByteStream output; + protected ConstPoolWriter constPool; + private int fieldCount; + + FieldWriter(ConstPoolWriter cp) { + output = new ByteStream(128); + constPool = cp; + fieldCount = 0; + } + + /** + * Adds a new field. + * + * @param accessFlags access flags. + * @param name the field name. + * @param descriptor the field type. + * @param aw the attributes of the field. may be null. + * @see AccessFlag + */ + public void add(int accessFlags, String name, String descriptor, AttributeWriter aw) { + int nameIndex = constPool.addUtf8Info(name); + int descIndex = constPool.addUtf8Info(descriptor); + add(accessFlags, nameIndex, descIndex, aw); + } + + /** + * Adds a new field. + * + * @param accessFlags access flags. + * @param name the field name. an index indicating its <code>CONSTANT_Utf8_info</code>. + * @param descriptor the field type. an index indicating its <code>CONSTANT_Utf8_info</code>. + * @param aw the attributes of the field. may be null. + * @see AccessFlag + */ + public void add(int accessFlags, int name, int descriptor, AttributeWriter aw) { + ++fieldCount; + output.writeShort(accessFlags); + output.writeShort(name); + output.writeShort(descriptor); + writeAttribute(output, aw, 0); + } + + int size() { return fieldCount; } + + int dataSize() { return output.size(); } + + /** + * Writes the added fields. + */ + void write(OutputStream out) throws IOException { + output.writeTo(out); + } + } + + /** + * Method. + */ + public static final class MethodWriter { + protected ByteStream output; + protected ConstPoolWriter constPool; + private int methodCount; + protected int codeIndex; + protected int throwsIndex; + protected int stackIndex; + + private int startPos; + private boolean isAbstract; + private int catchPos; + private int catchCount; + + MethodWriter(ConstPoolWriter cp) { + output = new ByteStream(256); + constPool = cp; + methodCount = 0; + codeIndex = 0; + throwsIndex = 0; + stackIndex = 0; + } + + /** + * Starts Adding a new method. + * + * @param accessFlags access flags. + * @param name the method name. + * @param descriptor the method signature. + * @param exceptions throws clause. It may be null. + * The class names must be the JVM-internal + * representations like <code>java/lang/Exception</code>. + * @param aw attributes to the <code>Method_info</code>. + */ + public void begin(int accessFlags, String name, String descriptor, + String[] exceptions, AttributeWriter aw) { + int nameIndex = constPool.addUtf8Info(name); + int descIndex = constPool.addUtf8Info(descriptor); + int[] intfs; + if (exceptions == null) + intfs = null; + else + intfs = constPool.addClassInfo(exceptions); + + begin(accessFlags, nameIndex, descIndex, intfs, aw); + } + + /** + * Starts adding a new method. + * + * @param accessFlags access flags. + * @param name the method name. an index indicating its <code>CONSTANT_Utf8_info</code>. + * @param descriptor the field type. an index indicating its <code>CONSTANT_Utf8_info</code>. + * @param exceptions throws clause. indexes indicating <code>CONSTANT_Class_info</code>s. + * It may be null. + * @param aw attributes to the <code>Method_info</code>. + */ + public void begin(int accessFlags, int name, int descriptor, int[] exceptions, AttributeWriter aw) { + ++methodCount; + output.writeShort(accessFlags); + output.writeShort(name); + output.writeShort(descriptor); + isAbstract = (accessFlags & AccessFlag.ABSTRACT) != 0; + + int attrCount = isAbstract ? 0 : 1; + if (exceptions != null) + ++attrCount; + + writeAttribute(output, aw, attrCount); + + if (exceptions != null) + writeThrows(exceptions); + + if (!isAbstract) { + if (codeIndex == 0) + codeIndex = constPool.addUtf8Info(CodeAttribute.tag); + + startPos = output.getPos(); + output.writeShort(codeIndex); + output.writeBlank(12); // attribute_length, maxStack, maxLocals, code_lenth + } + + catchPos = -1; + catchCount = 0; + } + + private void writeThrows(int[] exceptions) { + if (throwsIndex == 0) + throwsIndex = constPool.addUtf8Info(ExceptionsAttribute.tag); + + output.writeShort(throwsIndex); + output.writeInt(exceptions.length * 2 + 2); + output.writeShort(exceptions.length); + for (int i = 0; i < exceptions.length; i++) + output.writeShort(exceptions[i]); + } + + /** + * Appends an 8bit value of bytecode. + * + * @see Opcode + */ + public void add(int b) { + output.write(b); + } + + /** + * Appends a 16bit value of bytecode. + */ + public void add16(int b) { + output.writeShort(b); + } + + /** + * Appends a 32bit value of bytecode. + */ + public void add32(int b) { + output.writeInt(b); + } + + /** + * Appends a invokevirtual, inovkespecial, or invokestatic bytecode. + * + * @see Opcode + */ + public void addInvoke(int opcode, String targetClass, String methodName, + String descriptor) { + int target = constPool.addClassInfo(targetClass); + int nt = constPool.addNameAndTypeInfo(methodName, descriptor); + int method = constPool.addMethodrefInfo(target, nt); + add(opcode); + add16(method); + } + + /** + * Ends appending bytecode. + */ + public void codeEnd(int maxStack, int maxLocals) { + if (!isAbstract) { + output.writeShort(startPos + 6, maxStack); + output.writeShort(startPos + 8, maxLocals); + output.writeInt(startPos + 10, output.getPos() - startPos - 14); // code_length + catchPos = output.getPos(); + catchCount = 0; + output.writeShort(0); // number of catch clauses + } + } + + /** + * Appends an <code>exception_table</code> entry to the + * <code>Code_attribute</code>. This method is available + * only after the <code>codeEnd</code> method is called. + * + * @param catchType an index indicating a <code>CONSTANT_Class_info</code>. + */ + public void addCatch(int startPc, int endPc, int handlerPc, int catchType) { + ++catchCount; + output.writeShort(startPc); + output.writeShort(endPc); + output.writeShort(handlerPc); + output.writeShort(catchType); + } + + /** + * Ends adding a new method. The <code>add</code> method must be + * called before the <code>end</code> method is called. + * + * @param smap a stack map table. may be null. + * @param aw attributes to the <code>Code_attribute</code>. + * may be null. + */ + public void end(StackMapTable.Writer smap, AttributeWriter aw) { + if (isAbstract) + return; + + // exception_table_length + output.writeShort(catchPos, catchCount); + + int attrCount = smap == null ? 0 : 1; + writeAttribute(output, aw, attrCount); + + if (smap != null) { + if (stackIndex == 0) + stackIndex = constPool.addUtf8Info(StackMapTable.tag); + + output.writeShort(stackIndex); + byte[] data = smap.toByteArray(); + output.writeInt(data.length); + output.write(data); + } + + // Code attribute_length + output.writeInt(startPos + 2, output.getPos() - startPos - 6); + } + + int size() { return methodCount; } + + int dataSize() { return output.size(); } + + /** + * Writes the added methods. + */ + void write(OutputStream out) throws IOException { + output.writeTo(out); + } + } + + /** + * Constant Pool. + */ + public static final class ConstPoolWriter { + ByteStream output; + protected int startPos; + protected int num; + + ConstPoolWriter(ByteStream out) { + output = out; + startPos = out.getPos(); + num = 1; + output.writeShort(1); // number of entries + } + + /** + * Makes <code>CONSTANT_Class_info</code> objects for each class name. + * + * @return an array of indexes indicating <code>CONSTANT_Class_info</code>s. + */ + public int[] addClassInfo(String[] classNames) { + int n = classNames.length; + int[] result = new int[n]; + for (int i = 0; i < n; i++) + result[i] = addClassInfo(classNames[i]); + + return result; + } + + /** + * Adds a new <code>CONSTANT_Class_info</code> structure. + * + * <p>This also adds a <code>CONSTANT_Utf8_info</code> structure + * for storing the class name. + * + * @param jvmname the JVM-internal representation of a class name. + * e.g. <code>java/lang/Object</code>. + * @return the index of the added entry. + */ + public int addClassInfo(String jvmname) { + int utf8 = addUtf8Info(jvmname); + output.write(ClassInfo.tag); + output.writeShort(utf8); + return num++; + } + + /** + * Adds a new <code>CONSTANT_Class_info</code> structure. + * + * @param name <code>name_index</code> + * @return the index of the added entry. + */ + public int addClassInfo(int name) { + output.write(ClassInfo.tag); + output.writeShort(name); + return num++; + } + + /** + * Adds a new <code>CONSTANT_NameAndType_info</code> structure. + * + * @param name <code>name_index</code> + * @param type <code>descriptor_index</code> + * @return the index of the added entry. + */ + public int addNameAndTypeInfo(String name, String type) { + return addNameAndTypeInfo(addUtf8Info(name), addUtf8Info(type)); + } + + /** + * Adds a new <code>CONSTANT_NameAndType_info</code> structure. + * + * @param name <code>name_index</code> + * @param type <code>descriptor_index</code> + * @return the index of the added entry. + */ + public int addNameAndTypeInfo(int name, int type) { + output.write(NameAndTypeInfo.tag); + output.writeShort(name); + output.writeShort(type); + return num++; + } + + /** + * Adds a new <code>CONSTANT_Fieldref_info</code> structure. + * + * @param classInfo <code>class_index</code> + * @param nameAndTypeInfo <code>name_and_type_index</code>. + * @return the index of the added entry. + */ + public int addFieldrefInfo(int classInfo, int nameAndTypeInfo) { + output.write(FieldrefInfo.tag); + output.writeShort(classInfo); + output.writeShort(nameAndTypeInfo); + return num++; + } + + /** + * Adds a new <code>CONSTANT_Methodref_info</code> structure. + * + * @param classInfo <code>class_index</code> + * @param nameAndTypeInfo <code>name_and_type_index</code>. + * @return the index of the added entry. + */ + public int addMethodrefInfo(int classInfo, int nameAndTypeInfo) { + output.write(MethodrefInfo.tag); + output.writeShort(classInfo); + output.writeShort(nameAndTypeInfo); + return num++; + } + + /** + * Adds a new <code>CONSTANT_InterfaceMethodref_info</code> + * structure. + * + * @param classInfo <code>class_index</code> + * @param nameAndTypeInfo <code>name_and_type_index</code>. + * @return the index of the added entry. + */ + public int addInterfaceMethodrefInfo(int classInfo, + int nameAndTypeInfo) { + output.write(InterfaceMethodrefInfo.tag); + output.writeShort(classInfo); + output.writeShort(nameAndTypeInfo); + return num++; + } + + /** + * Adds a new <code>CONSTANT_String_info</code> + * structure. + * + * <p>This also adds a new <code>CONSTANT_Utf8_info</code> + * structure. + * + * @return the index of the added entry. + */ + public int addStringInfo(String str) { + int utf8 = addUtf8Info(str); + output.write(StringInfo.tag); + output.writeShort(utf8); + return num++; + } + + /** + * Adds a new <code>CONSTANT_Integer_info</code> + * structure. + * + * @return the index of the added entry. + */ + public int addIntegerInfo(int i) { + output.write(IntegerInfo.tag); + output.writeInt(i); + return num++; + } + + /** + * Adds a new <code>CONSTANT_Float_info</code> + * structure. + * + * @return the index of the added entry. + */ + public int addFloatInfo(float f) { + output.write(FloatInfo.tag); + output.writeFloat(f); + return num++; + } + + /** + * Adds a new <code>CONSTANT_Long_info</code> + * structure. + * + * @return the index of the added entry. + */ + public int addLongInfo(long l) { + output.write(LongInfo.tag); + output.writeLong(l); + int n = num; + num += 2; + return n; + } + + /** + * Adds a new <code>CONSTANT_Double_info</code> + * structure. + * + * @return the index of the added entry. + */ + public int addDoubleInfo(double d) { + output.write(DoubleInfo.tag); + output.writeDouble(d); + int n = num; + num += 2; + return n; + } + + /** + * Adds a new <code>CONSTANT_Utf8_info</code> + * structure. + * + * @return the index of the added entry. + */ + public int addUtf8Info(String utf8) { + output.write(Utf8Info.tag); + output.writeUTF(utf8); + return num++; + } + + /** + * Writes the contents of this class pool. + */ + void end() { + output.writeShort(startPos, num); + } + } +} diff --git a/src/main/javassist/bytecode/CodeAnalyzer.java b/src/main/javassist/bytecode/CodeAnalyzer.java new file mode 100644 index 0000000..a078e1f --- /dev/null +++ b/src/main/javassist/bytecode/CodeAnalyzer.java @@ -0,0 +1,262 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.bytecode; + +/** + * Utility for computing <code>max_stack</code>. + */ +class CodeAnalyzer implements Opcode { + private ConstPool constPool; + private CodeAttribute codeAttr; + + public CodeAnalyzer(CodeAttribute ca) { + codeAttr = ca; + constPool = ca.getConstPool(); + } + + public int computeMaxStack() + throws BadBytecode + { + /* d = stack[i] + * d == 0: not visited + * d > 0: the depth is d - 1 after executing the bytecode at i. + * d < 0: not visited. the initial depth (before execution) is 1 - d. + */ + CodeIterator ci = codeAttr.iterator(); + int length = ci.getCodeLength(); + int[] stack = new int[length]; + constPool = codeAttr.getConstPool(); + initStack(stack, codeAttr); + boolean repeat; + do { + repeat = false; + for (int i = 0; i < length; ++i) + if (stack[i] < 0) { + repeat = true; + visitBytecode(ci, stack, i); + } + } while (repeat); + + int maxStack = 1; + for (int i = 0; i < length; ++i) + if (stack[i] > maxStack) + maxStack = stack[i]; + + return maxStack - 1; // the base is 1. + } + + private void initStack(int[] stack, CodeAttribute ca) { + stack[0] = -1; + ExceptionTable et = ca.getExceptionTable(); + if (et != null) { + int size = et.size(); + for (int i = 0; i < size; ++i) + stack[et.handlerPc(i)] = -2; // an exception is on stack + } + } + + private void visitBytecode(CodeIterator ci, int[] stack, int index) + throws BadBytecode + { + int codeLength = stack.length; + ci.move(index); + int stackDepth = -stack[index]; + int[] jsrDepth = new int[1]; + jsrDepth[0] = -1; + while (ci.hasNext()) { + index = ci.next(); + stack[index] = stackDepth; + int op = ci.byteAt(index); + stackDepth = visitInst(op, ci, index, stackDepth); + if (stackDepth < 1) + throw new BadBytecode("stack underflow at " + index); + + if (processBranch(op, ci, index, codeLength, stack, stackDepth, jsrDepth)) + break; + + if (isEnd(op)) // return, ireturn, athrow, ... + break; + + if (op == JSR || op == JSR_W) + --stackDepth; + } + } + + private boolean processBranch(int opcode, CodeIterator ci, int index, + int codeLength, int[] stack, int stackDepth, int[] jsrDepth) + throws BadBytecode + { + if ((IFEQ <= opcode && opcode <= IF_ACMPNE) + || opcode == IFNULL || opcode == IFNONNULL) { + int target = index + ci.s16bitAt(index + 1); + checkTarget(index, target, codeLength, stack, stackDepth); + } + else { + int target, index2; + switch (opcode) { + case GOTO : + target = index + ci.s16bitAt(index + 1); + checkTarget(index, target, codeLength, stack, stackDepth); + return true; + case GOTO_W : + target = index + ci.s32bitAt(index + 1); + checkTarget(index, target, codeLength, stack, stackDepth); + return true; + case JSR : + case JSR_W : + if (opcode == JSR) + target = index + ci.s16bitAt(index + 1); + else + target = index + ci.s32bitAt(index + 1); + + checkTarget(index, target, codeLength, stack, stackDepth); + /* + * It is unknown which RET comes back to this JSR. + * So we assume that if the stack depth at one JSR instruction + * is N, then it is also N at other JSRs and N - 1 at all RET + * instructions. Note that STACK_GROW[JSR] is 1 since it pushes + * a return address on the operand stack. + */ + if (jsrDepth[0] < 0) { + jsrDepth[0] = stackDepth; + return false; + } + else if (stackDepth == jsrDepth[0]) + return false; + else + throw new BadBytecode( + "sorry, cannot compute this data flow due to JSR: " + + stackDepth + "," + jsrDepth[0]); + case RET : + if (jsrDepth[0] < 0) { + jsrDepth[0] = stackDepth + 1; + return false; + } + else if (stackDepth + 1 == jsrDepth[0]) + return true; + else + throw new BadBytecode( + "sorry, cannot compute this data flow due to RET: " + + stackDepth + "," + jsrDepth[0]); + case LOOKUPSWITCH : + case TABLESWITCH : + index2 = (index & ~3) + 4; + target = index + ci.s32bitAt(index2); + checkTarget(index, target, codeLength, stack, stackDepth); + if (opcode == LOOKUPSWITCH) { + int npairs = ci.s32bitAt(index2 + 4); + index2 += 12; + for (int i = 0; i < npairs; ++i) { + target = index + ci.s32bitAt(index2); + checkTarget(index, target, codeLength, + stack, stackDepth); + index2 += 8; + } + } + else { + int low = ci.s32bitAt(index2 + 4); + int high = ci.s32bitAt(index2 + 8); + int n = high - low + 1; + index2 += 12; + for (int i = 0; i < n; ++i) { + target = index + ci.s32bitAt(index2); + checkTarget(index, target, codeLength, + stack, stackDepth); + index2 += 4; + } + } + + return true; // always branch. + } + } + + return false; // may not branch. + } + + private void checkTarget(int opIndex, int target, int codeLength, + int[] stack, int stackDepth) + throws BadBytecode + { + if (target < 0 || codeLength <= target) + throw new BadBytecode("bad branch offset at " + opIndex); + + int d = stack[target]; + if (d == 0) + stack[target] = -stackDepth; + else if (d != stackDepth && d != -stackDepth) + throw new BadBytecode("verification error (" + stackDepth + + "," + d + ") at " + opIndex); + } + + private static boolean isEnd(int opcode) { + return (IRETURN <= opcode && opcode <= RETURN) || opcode == ATHROW; + } + + /** + * Visits an instruction. + */ + private int visitInst(int op, CodeIterator ci, int index, int stack) + throws BadBytecode + { + String desc; + switch (op) { + case GETFIELD : + stack += getFieldSize(ci, index) - 1; + break; + case PUTFIELD : + stack -= getFieldSize(ci, index) + 1; + break; + case GETSTATIC : + stack += getFieldSize(ci, index); + break; + case PUTSTATIC : + stack -= getFieldSize(ci, index); + break; + case INVOKEVIRTUAL : + case INVOKESPECIAL : + desc = constPool.getMethodrefType(ci.u16bitAt(index + 1)); + stack += Descriptor.dataSize(desc) - 1; + break; + case INVOKESTATIC : + desc = constPool.getMethodrefType(ci.u16bitAt(index + 1)); + stack += Descriptor.dataSize(desc); + break; + case INVOKEINTERFACE : + desc = constPool.getInterfaceMethodrefType( + ci.u16bitAt(index + 1)); + stack += Descriptor.dataSize(desc) - 1; + break; + case ATHROW : + stack = 1; // the stack becomes empty (1 means no values). + break; + case MULTIANEWARRAY : + stack += 1 - ci.byteAt(index + 3); + break; + case WIDE : + op = ci.byteAt(index + 1); + // don't break here. + default : + stack += STACK_GROW[op]; + } + + return stack; + } + + private int getFieldSize(CodeIterator ci, int index) { + String desc = constPool.getFieldrefType(ci.u16bitAt(index + 1)); + return Descriptor.dataSize(desc); + } +} diff --git a/src/main/javassist/bytecode/CodeAttribute.java b/src/main/javassist/bytecode/CodeAttribute.java new file mode 100644 index 0000000..99dca1d --- /dev/null +++ b/src/main/javassist/bytecode/CodeAttribute.java @@ -0,0 +1,585 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.bytecode; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.List; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.Map; + +/** + * <code>Code_attribute</code>. + * + * <p>To browse the <code>code</code> field of + * a <code>Code_attribute</code> structure, + * use <code>CodeIterator</code>. + * + * @see CodeIterator + */ +public class CodeAttribute extends AttributeInfo implements Opcode { + /** + * The name of this attribute <code>"Code"</code>. + */ + public static final String tag = "Code"; + + // code[] is stored in AttributeInfo.info. + + private int maxStack; + private int maxLocals; + private ExceptionTable exceptions; + private ArrayList attributes; + + /** + * Constructs a <code>Code_attribute</code>. + * + * @param cp constant pool table + * @param stack <code>max_stack</code> + * @param locals <code>max_locals</code> + * @param code <code>code[]</code> + * @param etable <code>exception_table[]</code> + */ + public CodeAttribute(ConstPool cp, int stack, int locals, byte[] code, + ExceptionTable etable) + { + super(cp, tag); + maxStack = stack; + maxLocals = locals; + info = code; + exceptions = etable; + attributes = new ArrayList(); + } + + /** + * Constructs a copy of <code>Code_attribute</code>. + * Specified class names are replaced during the copy. + * + * @param cp constant pool table. + * @param src source Code attribute. + * @param classnames pairs of replaced and substituted + * class names. + */ + private CodeAttribute(ConstPool cp, CodeAttribute src, Map classnames) + throws BadBytecode + { + super(cp, tag); + + maxStack = src.getMaxStack(); + maxLocals = src.getMaxLocals(); + exceptions = src.getExceptionTable().copy(cp, classnames); + attributes = new ArrayList(); + List src_attr = src.getAttributes(); + int num = src_attr.size(); + for (int i = 0; i < num; ++i) { + AttributeInfo ai = (AttributeInfo)src_attr.get(i); + attributes.add(ai.copy(cp, classnames)); + } + + info = src.copyCode(cp, classnames, exceptions, this); + } + + CodeAttribute(ConstPool cp, int name_id, DataInputStream in) + throws IOException + { + super(cp, name_id, (byte[])null); + int attr_len = in.readInt(); + + maxStack = in.readUnsignedShort(); + maxLocals = in.readUnsignedShort(); + + int code_len = in.readInt(); + info = new byte[code_len]; + in.readFully(info); + + exceptions = new ExceptionTable(cp, in); + + attributes = new ArrayList(); + int num = in.readUnsignedShort(); + for (int i = 0; i < num; ++i) + attributes.add(AttributeInfo.read(cp, in)); + } + + /** + * Makes a copy. Class names are replaced according to the + * given <code>Map</code> object. + * + * @param newCp the constant pool table used by the new copy. + * @param classnames pairs of replaced and substituted + * class names. + * @exception RuntimeCopyException if a <code>BadBytecode</code> + * exception is thrown, it is + * converted into + * <code>RuntimeCopyException</code>. + * + * @return <code>CodeAttribute</code> object. + */ + public AttributeInfo copy(ConstPool newCp, Map classnames) + throws RuntimeCopyException + { + try { + return new CodeAttribute(newCp, this, classnames); + } + catch (BadBytecode e) { + throw new RuntimeCopyException("bad bytecode. fatal?"); + } + } + + /** + * An exception that may be thrown by <code>copy()</code> + * in <code>CodeAttribute</code>. + */ + public static class RuntimeCopyException extends RuntimeException { + /** + * Constructs an exception. + */ + public RuntimeCopyException(String s) { + super(s); + } + } + + /** + * Returns the length of this <code>attribute_info</code> + * structure. + * The returned value is <code>attribute_length + 6</code>. + */ + public int length() { + return 18 + info.length + exceptions.size() * 8 + + AttributeInfo.getLength(attributes); + } + + void write(DataOutputStream out) throws IOException { + out.writeShort(name); // attribute_name_index + out.writeInt(length() - 6); // attribute_length + out.writeShort(maxStack); // max_stack + out.writeShort(maxLocals); // max_locals + out.writeInt(info.length); // code_length + out.write(info); // code + exceptions.write(out); + out.writeShort(attributes.size()); // attributes_count + AttributeInfo.writeAll(attributes, out); // attributes + } + + /** + * This method is not available. + * + * @throws java.lang.UnsupportedOperationException always thrown. + */ + public byte[] get() { + throw new UnsupportedOperationException("CodeAttribute.get()"); + } + + /** + * This method is not available. + * + * @throws java.lang.UnsupportedOperationException always thrown. + */ + public void set(byte[] newinfo) { + throw new UnsupportedOperationException("CodeAttribute.set()"); + } + + void renameClass(String oldname, String newname) { + AttributeInfo.renameClass(attributes, oldname, newname); + } + + void renameClass(Map classnames) { + AttributeInfo.renameClass(attributes, classnames); + } + + void getRefClasses(Map classnames) { + AttributeInfo.getRefClasses(attributes, classnames); + } + + /** + * Returns the name of the class declaring the method including + * this code attribute. + */ + public String getDeclaringClass() { + ConstPool cp = getConstPool(); + return cp.getClassName(); + } + + /** + * Returns <code>max_stack</code>. + */ + public int getMaxStack() { + return maxStack; + } + + /** + * Sets <code>max_stack</code>. + */ + public void setMaxStack(int value) { + maxStack = value; + } + + /** + * Computes the maximum stack size and sets <code>max_stack</code> + * to the computed size. + * + * @throws BadBytecode if this method fails in computing. + * @return the newly computed value of <code>max_stack</code> + */ + public int computeMaxStack() throws BadBytecode { + maxStack = new CodeAnalyzer(this).computeMaxStack(); + return maxStack; + } + + /** + * Returns <code>max_locals</code>. + */ + public int getMaxLocals() { + return maxLocals; + } + + /** + * Sets <code>max_locals</code>. + */ + public void setMaxLocals(int value) { + maxLocals = value; + } + + /** + * Returns <code>code_length</code>. + */ + public int getCodeLength() { + return info.length; + } + + /** + * Returns <code>code[]</code>. + */ + public byte[] getCode() { + return info; + } + + /** + * Sets <code>code[]</code>. + */ + void setCode(byte[] newinfo) { super.set(newinfo); } + + /** + * Makes a new iterator for reading this code attribute. + */ + public CodeIterator iterator() { + return new CodeIterator(this); + } + + /** + * Returns <code>exception_table[]</code>. + */ + public ExceptionTable getExceptionTable() { return exceptions; } + + /** + * Returns <code>attributes[]</code>. + * It returns a list of <code>AttributeInfo</code>. + * A new element can be added to the returned list + * and an existing element can be removed from the list. + * + * @see AttributeInfo + */ + public List getAttributes() { return attributes; } + + /** + * Returns the attribute with the specified name. + * If it is not found, this method returns null. + * + * @param name attribute name + * @return an <code>AttributeInfo</code> object or null. + */ + public AttributeInfo getAttribute(String name) { + return AttributeInfo.lookup(attributes, name); + } + + /** + * Adds a stack map table. If another copy of stack map table + * is already contained, the old one is removed. + * + * @param smt the stack map table added to this code attribute. + * If it is null, a new stack map is not added. + * Only the old stack map is removed. + */ + public void setAttribute(StackMapTable smt) { + AttributeInfo.remove(attributes, StackMapTable.tag); + if (smt != null) + attributes.add(smt); + } + + /** + * Adds a stack map table for J2ME (CLDC). If another copy of stack map table + * is already contained, the old one is removed. + * + * @param sm the stack map table added to this code attribute. + * If it is null, a new stack map is not added. + * Only the old stack map is removed. + * @since 3.12 + */ + public void setAttribute(StackMap sm) { + AttributeInfo.remove(attributes, StackMap.tag); + if (sm != null) + attributes.add(sm); + } + + /** + * Copies code. + */ + private byte[] copyCode(ConstPool destCp, Map classnames, + ExceptionTable etable, CodeAttribute destCa) + throws BadBytecode + { + int len = getCodeLength(); + byte[] newCode = new byte[len]; + destCa.info = newCode; + LdcEntry ldc = copyCode(this.info, 0, len, this.getConstPool(), + newCode, destCp, classnames); + return LdcEntry.doit(newCode, ldc, etable, destCa); + } + + private static LdcEntry copyCode(byte[] code, int beginPos, int endPos, + ConstPool srcCp, byte[] newcode, + ConstPool destCp, Map classnameMap) + throws BadBytecode + { + int i2, index; + LdcEntry ldcEntry = null; + + for (int i = beginPos; i < endPos; i = i2) { + i2 = CodeIterator.nextOpcode(code, i); + byte c = code[i]; + newcode[i] = c; + switch (c & 0xff) { + case LDC_W : + case LDC2_W : + case GETSTATIC : + case PUTSTATIC : + case GETFIELD : + case PUTFIELD : + case INVOKEVIRTUAL : + case INVOKESPECIAL : + case INVOKESTATIC : + case NEW : + case ANEWARRAY : + case CHECKCAST : + case INSTANCEOF : + copyConstPoolInfo(i + 1, code, srcCp, newcode, destCp, + classnameMap); + break; + case LDC : + index = code[i + 1] & 0xff; + index = srcCp.copy(index, destCp, classnameMap); + if (index < 0x100) + newcode[i + 1] = (byte)index; + else { + newcode[i] = NOP; + newcode[i + 1] = NOP; + LdcEntry ldc = new LdcEntry(); + ldc.where = i; + ldc.index = index; + ldc.next = ldcEntry; + ldcEntry = ldc; + } + break; + case INVOKEINTERFACE : + copyConstPoolInfo(i + 1, code, srcCp, newcode, destCp, + classnameMap); + newcode[i + 3] = code[i + 3]; + newcode[i + 4] = code[i + 4]; + break; + case MULTIANEWARRAY : + copyConstPoolInfo(i + 1, code, srcCp, newcode, destCp, + classnameMap); + newcode[i + 3] = code[i + 3]; + break; + default : + while (++i < i2) + newcode[i] = code[i]; + + break; + } + } + + return ldcEntry; + } + + private static void copyConstPoolInfo(int i, byte[] code, ConstPool srcCp, + byte[] newcode, ConstPool destCp, + Map classnameMap) { + int index = ((code[i] & 0xff) << 8) | (code[i + 1] & 0xff); + index = srcCp.copy(index, destCp, classnameMap); + newcode[i] = (byte)(index >> 8); + newcode[i + 1] = (byte)index; + } + + static class LdcEntry { + LdcEntry next; + int where; + int index; + + static byte[] doit(byte[] code, LdcEntry ldc, ExceptionTable etable, + CodeAttribute ca) + throws BadBytecode + { + if (ldc != null) + code = CodeIterator.changeLdcToLdcW(code, etable, ca, ldc); + + /* The original code was the following: + + while (ldc != null) { + int where = ldc.where; + code = CodeIterator.insertGapCore0(code, where, 1, false, etable, ca); + code[where] = (byte)Opcode.LDC_W; + ByteArray.write16bit(ldc.index, code, where + 1); + ldc = ldc.next; + } + + But this code does not support a large method > 32KB. + */ + + return code; + } + } + + /** + * Changes the index numbers of the local variables + * to append a new parameter. + * This method does not update <code>LocalVariableAttribute</code>, + * <code>StackMapTable</code>, or <code>StackMap</code>. + * These attributes must be explicitly updated. + * + * @param where the index of the new parameter. + * @param size the type size of the new parameter (1 or 2). + * + * @see LocalVariableAttribute#shiftIndex(int, int) + * @see StackMapTable#insertLocal(int, int, int) + * @see StackMap#insertLocal(int, int, int) + */ + public void insertLocalVar(int where, int size) throws BadBytecode { + CodeIterator ci = iterator(); + while (ci.hasNext()) + shiftIndex(ci, where, size); + + setMaxLocals(getMaxLocals() + size); + } + + /** + * @param lessThan If the index of the local variable is + * less than this value, it does not change. + * Otherwise, the index is increased. + * @param delta the indexes of the local variables are + * increased by this value. + */ + private static void shiftIndex(CodeIterator ci, int lessThan, int delta) throws BadBytecode { + int index = ci.next(); + int opcode = ci.byteAt(index); + if (opcode < ILOAD) + return; + else if (opcode < IASTORE) { + if (opcode < ILOAD_0) { + // iload, lload, fload, dload, aload + shiftIndex8(ci, index, opcode, lessThan, delta); + } + else if (opcode < IALOAD) { + // iload_0, ..., aload_3 + shiftIndex0(ci, index, opcode, lessThan, delta, ILOAD_0, ILOAD); + } + else if (opcode < ISTORE) + return; + else if (opcode < ISTORE_0) { + // istore, lstore, ... + shiftIndex8(ci, index, opcode, lessThan, delta); + } + else { + // istore_0, ..., astore_3 + shiftIndex0(ci, index, opcode, lessThan, delta, ISTORE_0, ISTORE); + } + } + else if (opcode == IINC) { + int var = ci.byteAt(index + 1); + if (var < lessThan) + return; + + var += delta; + if (var < 0x100) + ci.writeByte(var, index + 1); + else { + int plus = (byte)ci.byteAt(index + 2); + int pos = ci.insertExGap(3); + ci.writeByte(WIDE, pos - 3); + ci.writeByte(IINC, pos - 2); + ci.write16bit(var, pos - 1); + ci.write16bit(plus, pos + 1); + } + } + else if (opcode == RET) + shiftIndex8(ci, index, opcode, lessThan, delta); + else if (opcode == WIDE) { + int var = ci.u16bitAt(index + 2); + if (var < lessThan) + return; + + var += delta; + ci.write16bit(var, index + 2); + } + } + + private static void shiftIndex8(CodeIterator ci, int index, int opcode, + int lessThan, int delta) + throws BadBytecode + { + int var = ci.byteAt(index + 1); + if (var < lessThan) + return; + + var += delta; + if (var < 0x100) + ci.writeByte(var, index + 1); + else { + int pos = ci.insertExGap(2); + ci.writeByte(WIDE, pos - 2); + ci.writeByte(opcode, pos - 1); + ci.write16bit(var, pos); + } + } + + private static void shiftIndex0(CodeIterator ci, int index, int opcode, + int lessThan, int delta, + int opcode_i_0, int opcode_i) + throws BadBytecode + { + int var = (opcode - opcode_i_0) % 4; + if (var < lessThan) + return; + + var += delta; + if (var < 4) + ci.writeByte(opcode + delta, index); + else { + opcode = (opcode - opcode_i_0) / 4 + opcode_i; + if (var < 0x100) { + int pos = ci.insertExGap(1); + ci.writeByte(opcode, pos - 1); + ci.writeByte(var, pos); + } + else { + int pos = ci.insertExGap(3); + ci.writeByte(WIDE, pos - 1); + ci.writeByte(opcode, pos); + ci.write16bit(var, pos + 1); + } + } + } +} diff --git a/src/main/javassist/bytecode/CodeIterator.java b/src/main/javassist/bytecode/CodeIterator.java new file mode 100644 index 0000000..7781b70 --- /dev/null +++ b/src/main/javassist/bytecode/CodeIterator.java @@ -0,0 +1,1571 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.bytecode; + +import java.util.ArrayList; + +/** + * An iterator for editing a code attribute. + * + * <p>If there are multiple <code>CodeIterator</code>s referring to the + * same <code>Code_attribute</code>, then inserting a gap by one + * <code>CodeIterator</code> will break the other + * <code>CodeIterator</code>. + * + * <p>This iterator does not provide <code>remove()</code>. + * If a piece of code in a <code>Code_attribute</code> is unnecessary, + * it should be overwritten with <code>NOP</code>. + * + * @see CodeAttribute#iterator() + */ +public class CodeIterator implements Opcode { + protected CodeAttribute codeAttr; + protected byte[] bytecode; + protected int endPos; + protected int currentPos; + protected int mark; + + protected CodeIterator(CodeAttribute ca) { + codeAttr = ca; + bytecode = ca.getCode(); + begin(); + } + + /** + * Moves to the first instruction. + */ + public void begin() { + currentPos = mark = 0; + endPos = getCodeLength(); + } + + /** + * Moves to the given index. + * + * <p>The index of the next instruction is set to the given index. + * The successive call to <code>next()</code> + * returns the index that has been given to <code>move()</code>. + * + * <p>Note that the index is into the byte array returned by + * <code>get().getCode()</code>. + * + * @see CodeAttribute#getCode() + */ + public void move(int index) { + currentPos = index; + } + + /** + * Sets a mark to the bytecode at the given index. + * The mark can be used to track the position of that bytecode + * when code blocks are inserted. + * If a code block is inclusively inserted at the position of the + * bytecode, the mark is set to the inserted code block. + * + * @see #getMark() + * @since 3.11 + */ + public void setMark(int index) { + mark = index; + } + + /** + * Gets the index of the position of the mark set by + * <code>setMark</code>. + * + * @return the index of the position. + * @see #setMark(int) + * @since 3.11 + */ + public int getMark() { return mark; } + + /** + * Returns a Code attribute read with this iterator. + */ + public CodeAttribute get() { + return codeAttr; + } + + /** + * Returns <code>code_length</code> of <code>Code_attribute</code>. + */ + public int getCodeLength() { + return bytecode.length; + } + + /** + * Returns the unsigned 8bit value at the given index. + */ + public int byteAt(int index) { return bytecode[index] & 0xff; } + + /** + * Writes an 8bit value at the given index. + */ + public void writeByte(int value, int index) { + bytecode[index] = (byte)value; + } + + /** + * Returns the unsigned 16bit value at the given index. + */ + public int u16bitAt(int index) { + return ByteArray.readU16bit(bytecode, index); + } + + /** + * Returns the signed 16bit value at the given index. + */ + public int s16bitAt(int index) { + return ByteArray.readS16bit(bytecode, index); + } + + /** + * Writes a 16 bit integer at the index. + */ + public void write16bit(int value, int index) { + ByteArray.write16bit(value, bytecode, index); + } + + /** + * Returns the signed 32bit value at the given index. + */ + public int s32bitAt(int index) { + return ByteArray.read32bit(bytecode, index); + } + + /** + * Writes a 32bit integer at the index. + */ + public void write32bit(int value, int index) { + ByteArray.write32bit(value, bytecode, index); + } + + /** + * Writes a byte array at the index. + * + * @param code may be a zero-length array. + */ + public void write(byte[] code, int index) { + int len = code.length; + for (int j = 0; j < len; ++j) + bytecode[index++] = code[j]; + } + + /** + * Returns true if there is more instructions. + */ + public boolean hasNext() { return currentPos < endPos; } + + /** + * Returns the index of the next instruction + * (not the operand following the current opcode). + * + * <p>Note that the index is into the byte array returned by + * <code>get().getCode()</code>. + * + * @see CodeAttribute#getCode() + * @see CodeIterator#byteAt(int) + */ + public int next() throws BadBytecode { + int pos = currentPos; + currentPos = nextOpcode(bytecode, pos); + return pos; + } + + /** + * Obtains the value that the next call + * to <code>next()</code> will return. + * + * <p>This method is side-effects free. + * Successive calls to <code>lookAhead()</code> return the + * same value until <code>next()</code> is called. + */ + public int lookAhead() { + return currentPos; + } + + /** + * Moves to the instruction for + * either <code>super()</code> or <code>this()</code>. + * + * <p>This method skips all the instructions for computing arguments + * to <code>super()</code> or <code>this()</code>, which should be + * placed at the beginning of a constructor body. + * + * <p>This method returns the index of INVOKESPECIAL instruction + * executing <code>super()</code> or <code>this()</code>. + * A successive call to <code>next()</code> returns the + * index of the next instruction following that INVOKESPECIAL. + * + * <p>This method works only for a constructor. + * + * @return the index of the INVOKESPECIAL instruction, or -1 + * if a constructor invocation is not found. + */ + public int skipConstructor() throws BadBytecode { + return skipSuperConstructor0(-1); + } + + /** + * Moves to the instruction for <code>super()</code>. + * + * <p>This method skips all the instructions for computing arguments to + * <code>super()</code>, which should be + * placed at the beginning of a constructor body. + * + * <p>This method returns the index of INVOKESPECIAL instruction + * executing <code>super()</code>. + * A successive call to <code>next()</code> returns the + * index of the next instruction following that INVOKESPECIAL. + * + * <p>This method works only for a constructor. + * + * @return the index of the INVOKESPECIAL instruction, or -1 + * if a super constructor invocation is not found + * but <code>this()</code> is found. + */ + public int skipSuperConstructor() throws BadBytecode { + return skipSuperConstructor0(0); + } + + /** + * Moves to the instruction for <code>this()</code>. + * + * <p>This method skips all the instructions for computing arguments to + * <code>this()</code>, which should be + * placed at the beginning of a constructor body. + * + * <p>This method returns the index of INVOKESPECIAL instruction + * executing <code>this()</code>. + * A successive call to <code>next()</code> returns the + * index of the next instruction following that INVOKESPECIAL. + * + * <p>This method works only for a constructor. + * + * @return the index of the INVOKESPECIAL instruction, or -1 + * if a explicit constructor invocation is not found + * but <code>super()</code> is found. + */ + public int skipThisConstructor() throws BadBytecode { + return skipSuperConstructor0(1); + } + + /* skipSuper 1: this(), 0: super(), -1: both. + */ + private int skipSuperConstructor0(int skipThis) throws BadBytecode { + begin(); + ConstPool cp = codeAttr.getConstPool(); + String thisClassName = codeAttr.getDeclaringClass(); + int nested = 0; + while (hasNext()) { + int index = next(); + int c = byteAt(index); + if (c == NEW) + ++nested; + else if (c == INVOKESPECIAL) { + int mref = ByteArray.readU16bit(bytecode, index + 1); + if (cp.getMethodrefName(mref).equals(MethodInfo.nameInit)) + if (--nested < 0) { + if (skipThis < 0) + return index; + + String cname = cp.getMethodrefClassName(mref); + if (cname.equals(thisClassName) == (skipThis > 0)) + return index; + else + break; + } + } + } + + begin(); + return -1; + } + + /** + * Inserts the given bytecode sequence + * before the next instruction that would be returned by + * <code>next()</code> (not before the instruction returned + * by the last call to <code>next()</code>). + * Branch offsets and the exception table are also updated. + * + * <p>If the next instruction is at the beginning of a block statement, + * then the bytecode is inserted within that block. + * + * <p>An extra gap may be inserted at the end of the inserted + * bytecode sequence for adjusting alignment if the code attribute + * includes <code>LOOKUPSWITCH</code> or <code>TABLESWITCH</code>. + * + * @param code inserted bytecode sequence. + * @return the index indicating the first byte of the + * inserted byte sequence. + */ + public int insert(byte[] code) + throws BadBytecode + { + return insert0(currentPos, code, false); + } + + /** + * Inserts the given bytecode sequence + * before the instruction at the given index <code>pos</code>. + * Branch offsets and the exception table are also updated. + * + * <p>If the instruction at the given index is at the beginning + * of a block statement, + * then the bytecode is inserted within that block. + * + * <p>An extra gap may be inserted at the end of the inserted + * bytecode sequence for adjusting alignment if the code attribute + * includes <code>LOOKUPSWITCH</code> or <code>TABLESWITCH</code>. + * + * <p>The index at which the byte sequence is actually inserted + * might be different from pos since some other bytes might be + * inserted at other positions (e.g. to change <code>GOTO</code> + * to <code>GOTO_W</code>). + * + * @param pos the index at which a byte sequence is inserted. + * @param code inserted bytecode sequence. + */ + public void insert(int pos, byte[] code) throws BadBytecode { + insert0(pos, code, false); + } + + /** + * Inserts the given bytecode sequence + * before the instruction at the given index <code>pos</code>. + * Branch offsets and the exception table are also updated. + * + * <p>If the instruction at the given index is at the beginning + * of a block statement, + * then the bytecode is inserted within that block. + * + * <p>An extra gap may be inserted at the end of the inserted + * bytecode sequence for adjusting alignment if the code attribute + * includes <code>LOOKUPSWITCH</code> or <code>TABLESWITCH</code>. + * + * @param pos the index at which a byte sequence is inserted. + * @param code inserted bytecode sequence. + * @return the index indicating the first byte of the + * inserted byte sequence, which might be + * different from pos. + * @since 3.11 + */ + public int insertAt(int pos, byte[] code) throws BadBytecode { + return insert0(pos, code, false); + } + + /** + * Inserts the given bytecode sequence exclusively + * before the next instruction that would be returned by + * <code>next()</code> (not before the instruction returned + * by tha last call to <code>next()</code>). + * Branch offsets and the exception table are also updated. + * + * <p>If the next instruction is at the beginning of a block statement, + * then the bytecode is excluded from that block. + * + * <p>An extra gap may be inserted at the end of the inserted + * bytecode sequence for adjusting alignment if the code attribute + * includes <code>LOOKUPSWITCH</code> or <code>TABLESWITCH</code>. + * + * @param code inserted bytecode sequence. + * @return the index indicating the first byte of the + * inserted byte sequence. + */ + public int insertEx(byte[] code) + throws BadBytecode + { + return insert0(currentPos, code, true); + } + + /** + * Inserts the given bytecode sequence exclusively + * before the instruction at the given index <code>pos</code>. + * Branch offsets and the exception table are also updated. + * + * <p>If the instruction at the given index is at the beginning + * of a block statement, + * then the bytecode is excluded from that block. + * + * <p>An extra gap may be inserted at the end of the inserted + * bytecode sequence for adjusting alignment if the code attribute + * includes <code>LOOKUPSWITCH</code> or <code>TABLESWITCH</code>. + * + * <p>The index at which the byte sequence is actually inserted + * might be different from pos since some other bytes might be + * inserted at other positions (e.g. to change <code>GOTO</code> + * to <code>GOTO_W</code>). + * + * @param pos the index at which a byte sequence is inserted. + * @param code inserted bytecode sequence. + */ + public void insertEx(int pos, byte[] code) throws BadBytecode { + insert0(pos, code, true); + } + + /** + * Inserts the given bytecode sequence exclusively + * before the instruction at the given index <code>pos</code>. + * Branch offsets and the exception table are also updated. + * + * <p>If the instruction at the given index is at the beginning + * of a block statement, + * then the bytecode is excluded from that block. + * + * <p>An extra gap may be inserted at the end of the inserted + * bytecode sequence for adjusting alignment if the code attribute + * includes <code>LOOKUPSWITCH</code> or <code>TABLESWITCH</code>. + * + * @param pos the index at which a byte sequence is inserted. + * @param code inserted bytecode sequence. + * @return the index indicating the first byte of the + * inserted byte sequence, which might be + * different from pos. + * @since 3.11 + */ + public int insertExAt(int pos, byte[] code) throws BadBytecode { + return insert0(pos, code, true); + } + + /** + * @return the index indicating the first byte of the + * inserted byte sequence. + */ + private int insert0(int pos, byte[] code, boolean exclusive) + throws BadBytecode + { + int len = code.length; + if (len <= 0) + return pos; + + // currentPos will change. + pos = insertGapAt(pos, len, exclusive).position; + + int p = pos; + for (int j = 0; j < len; ++j) + bytecode[p++] = code[j]; + + return pos; + } + + /** + * Inserts a gap + * before the next instruction that would be returned by + * <code>next()</code> (not before the instruction returned + * by the last call to <code>next()</code>). + * Branch offsets and the exception table are also updated. + * The inserted gap is filled with NOP. The gap length may be + * extended to a multiple of 4. + * + * <p>If the next instruction is at the beginning of a block statement, + * then the gap is inserted within that block. + * + * @param length gap length + * @return the index indicating the first byte of the inserted gap. + */ + public int insertGap(int length) throws BadBytecode { + return insertGapAt(currentPos, length, false).position; + } + + /** + * Inserts a gap in front of the instruction at the given + * index <code>pos</code>. + * Branch offsets and the exception table are also updated. + * The inserted gap is filled with NOP. The gap length may be + * extended to a multiple of 4. + * + * <p>If the instruction at the given index is at the beginning + * of a block statement, + * then the gap is inserted within that block. + * + * @param pos the index at which a gap is inserted. + * @param length gap length. + * @return the length of the inserted gap. + * It might be bigger than <code>length</code>. + */ + public int insertGap(int pos, int length) throws BadBytecode { + return insertGapAt(pos, length, false).length; + } + + /** + * Inserts an exclusive gap + * before the next instruction that would be returned by + * <code>next()</code> (not before the instruction returned + * by the last call to <code>next()</code>). + * Branch offsets and the exception table are also updated. + * The inserted gap is filled with NOP. The gap length may be + * extended to a multiple of 4. + * + * <p>If the next instruction is at the beginning of a block statement, + * then the gap is excluded from that block. + * + * @param length gap length + * @return the index indicating the first byte of the inserted gap. + */ + public int insertExGap(int length) throws BadBytecode { + return insertGapAt(currentPos, length, true).position; + } + + /** + * Inserts an exclusive gap in front of the instruction at the given + * index <code>pos</code>. + * Branch offsets and the exception table are also updated. + * The inserted gap is filled with NOP. The gap length may be + * extended to a multiple of 4. + * + * <p>If the instruction at the given index is at the beginning + * of a block statement, + * then the gap is excluded from that block. + * + * @param pos the index at which a gap is inserted. + * @param length gap length. + * @return the length of the inserted gap. + * It might be bigger than <code>length</code>. + */ + public int insertExGap(int pos, int length) throws BadBytecode { + return insertGapAt(pos, length, true).length; + } + + /** + * An inserted gap. + * + * @since 3.11 + */ + public static class Gap { + /** + * The position of the gap. + */ + public int position; + + /** + * The length of the gap. + */ + public int length; + } + + /** + * Inserts an inclusive or exclusive gap in front of the instruction + * at the given index <code>pos</code>. + * Branch offsets and the exception table in the method body + * are also updated. The inserted gap is filled with NOP. + * The gap length may be extended to a multiple of 4. + * + * <p>Suppose that the instruction at the given index is at the + * beginning of a block statement. If the gap is inclusive, + * then it is included within that block. If the gap is exclusive, + * then it is excluded from that block. + * + * <p>The index at which the gap is actually inserted + * might be different from pos since some other bytes might be + * inserted at other positions (e.g. to change <code>GOTO</code> + * to <code>GOTO_W</code>). The index is available from the <code>Gap</code> + * object returned by this method. + * + * <p>Suppose that the gap is inserted at the position of + * the next instruction that would be returned by + * <code>next()</code> (not the last instruction returned + * by the last call to <code>next()</code>). The next + * instruction returned by <code>next()</code> after the gap is + * inserted is still the same instruction. It is not <code>NOP</code> + * at the first byte of the inserted gap. + * + * @param pos the index at which a gap is inserted. + * @param length gap length. + * @param exclusive true if exclusive, otherwise false. + * @return the position and the length of the inserted gap. + * @since 3.11 + */ + public Gap insertGapAt(int pos, int length, boolean exclusive) + throws BadBytecode + { + /** + * cursorPos indicates the next bytecode whichever exclusive is + * true or false. + */ + Gap gap = new Gap(); + if (length <= 0) { + gap.position = pos; + gap.length = 0; + return gap; + } + + byte[] c; + int length2; + if (bytecode.length + length > Short.MAX_VALUE) { + // currentPos might change after calling insertGapCore0w(). + c = insertGapCore0w(bytecode, pos, length, exclusive, + get().getExceptionTable(), codeAttr, gap); + pos = gap.position; + length2 = length; // == gap.length + } + else { + int cur = currentPos; + c = insertGapCore0(bytecode, pos, length, exclusive, + get().getExceptionTable(), codeAttr); + // insertGapCore0() never changes pos. + length2 = c.length - bytecode.length; + gap.position = pos; + gap.length = length2; + if (cur >= pos) + currentPos = cur + length2; + + if (mark > pos || (mark == pos && exclusive)) + mark += length2; + } + + codeAttr.setCode(c); + bytecode = c; + endPos = getCodeLength(); + updateCursors(pos, length2); + return gap; + } + + /** + * Is called when a gap is inserted. The default implementation is empty. + * A subclass can override this method so that cursors will be updated. + * + * @param pos the position where a gap is inserted. + * @param length the length of the gap. + */ + protected void updateCursors(int pos, int length) { + // empty + } + + /** + * Copies and inserts the entries in the given exception table + * at the beginning of the exception table in the code attribute + * edited by this object. + * + * @param offset the value added to the code positions included + * in the entries. + */ + public void insert(ExceptionTable et, int offset) { + codeAttr.getExceptionTable().add(0, et, offset); + } + + /** + * Appends the given bytecode sequence at the end. + * + * @param code the bytecode appended. + * @return the position of the first byte of the appended bytecode. + */ + public int append(byte[] code) { + int size = getCodeLength(); + int len = code.length; + if (len <= 0) + return size; + + appendGap(len); + byte[] dest = bytecode; + for (int i = 0; i < len; ++i) + dest[i + size] = code[i]; + + return size; + } + + /** + * Appends a gap at the end of the bytecode sequence. + * + * @param gapLength gap length + */ + public void appendGap(int gapLength) { + byte[] code = bytecode; + int codeLength = code.length; + byte[] newcode = new byte[codeLength + gapLength]; + + int i; + for (i = 0; i < codeLength; ++i) + newcode[i] = code[i]; + + for (i = codeLength; i < codeLength + gapLength; ++i) + newcode[i] = NOP; + + codeAttr.setCode(newcode); + bytecode = newcode; + endPos = getCodeLength(); + } + + /** + * Copies and appends the entries in the given exception table + * at the end of the exception table in the code attribute + * edited by this object. + * + * @param offset the value added to the code positions included + * in the entries. + */ + public void append(ExceptionTable et, int offset) { + ExceptionTable table = codeAttr.getExceptionTable(); + table.add(table.size(), et, offset); + } + + /* opcodeLegth is used for implementing nextOpcode(). + */ + private static final int opcodeLength[] = { + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, 2, 3, + 3, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 0, 0, 1, 1, 1, 1, 1, 1, 3, 3, + 3, 3, 3, 3, 3, 5, 0, 3, 2, 3, 1, 1, 3, 3, 1, 1, 0, 4, 3, 3, + 5, 5 + }; + // 0 .. UNUSED (186), LOOKUPSWITCH, TABLESWITCH, WIDE + + /** + * Calculates the index of the next opcode. + */ + static int nextOpcode(byte[] code, int index) + throws BadBytecode + { + int opcode; + try { + opcode = code[index] & 0xff; + } + catch (IndexOutOfBoundsException e) { + throw new BadBytecode("invalid opcode address"); + } + + try { + int len = opcodeLength[opcode]; + if (len > 0) + return index + len; + else if (opcode == WIDE) + if (code[index + 1] == (byte)IINC) // WIDE IINC + return index + 6; + else + return index + 4; // WIDE ... + else { + int index2 = (index & ~3) + 8; + if (opcode == LOOKUPSWITCH) { + int npairs = ByteArray.read32bit(code, index2); + return index2 + npairs * 8 + 4; + } + else if (opcode == TABLESWITCH) { + int low = ByteArray.read32bit(code, index2); + int high = ByteArray.read32bit(code, index2 + 4); + return index2 + (high - low + 1) * 4 + 8; + } + // else + // throw new BadBytecode(opcode); + } + } + catch (IndexOutOfBoundsException e) { + } + + // opcode is UNUSED or an IndexOutOfBoundsException was thrown. + throw new BadBytecode(opcode); + } + + // methods for implementing insertGap(). + + static class AlignmentException extends Exception {} + + /** + * insertGapCore0() inserts a gap (some NOPs). + * It cannot handle a long code sequence more than 32K. All branch offsets must be + * signed 16bits. + * + * If "where" is the beginning of a block statement and exclusive is false, + * then the inserted gap is also included in the block statement. + * "where" must indicate the first byte of an opcode. + * The inserted gap is filled with NOP. gapLength may be extended to + * a multiple of 4. + * + * This method was also called from CodeAttribute.LdcEntry.doit(). + * + * @param where It must indicate the first byte of an opcode. + */ + static byte[] insertGapCore0(byte[] code, int where, int gapLength, + boolean exclusive, ExceptionTable etable, CodeAttribute ca) + throws BadBytecode + { + if (gapLength <= 0) + return code; + + try { + return insertGapCore1(code, where, gapLength, exclusive, etable, ca); + } + catch (AlignmentException e) { + try { + return insertGapCore1(code, where, (gapLength + 3) & ~3, + exclusive, etable, ca); + } + catch (AlignmentException e2) { + throw new RuntimeException("fatal error?"); + } + } + } + + private static byte[] insertGapCore1(byte[] code, int where, int gapLength, + boolean exclusive, ExceptionTable etable, + CodeAttribute ca) + throws BadBytecode, AlignmentException + { + int codeLength = code.length; + byte[] newcode = new byte[codeLength + gapLength]; + insertGap2(code, where, gapLength, codeLength, newcode, exclusive); + etable.shiftPc(where, gapLength, exclusive); + LineNumberAttribute na + = (LineNumberAttribute)ca.getAttribute(LineNumberAttribute.tag); + if (na != null) + na.shiftPc(where, gapLength, exclusive); + + LocalVariableAttribute va = (LocalVariableAttribute)ca.getAttribute( + LocalVariableAttribute.tag); + if (va != null) + va.shiftPc(where, gapLength, exclusive); + + LocalVariableAttribute vta + = (LocalVariableAttribute)ca.getAttribute( + LocalVariableAttribute.typeTag); + if (vta != null) + vta.shiftPc(where, gapLength, exclusive); + + StackMapTable smt = (StackMapTable)ca.getAttribute(StackMapTable.tag); + if (smt != null) + smt.shiftPc(where, gapLength, exclusive); + + StackMap sm = (StackMap)ca.getAttribute(StackMap.tag); + if (sm != null) + sm.shiftPc(where, gapLength, exclusive); + + return newcode; + } + + private static void insertGap2(byte[] code, int where, int gapLength, + int endPos, byte[] newcode, boolean exclusive) + throws BadBytecode, AlignmentException + { + int nextPos; + int i = 0; + int j = 0; + for (; i < endPos; i = nextPos) { + if (i == where) { + int j2 = j + gapLength; + while (j < j2) + newcode[j++] = NOP; + } + + nextPos = nextOpcode(code, i); + int inst = code[i] & 0xff; + // if<cond>, if_icmp<cond>, if_acmp<cond>, goto, jsr + if ((153 <= inst && inst <= 168) + || inst == IFNULL || inst == IFNONNULL) { + /* 2bytes *signed* offset */ + int offset = (code[i + 1] << 8) | (code[i + 2] & 0xff); + offset = newOffset(i, offset, where, gapLength, exclusive); + newcode[j] = code[i]; + ByteArray.write16bit(offset, newcode, j + 1); + j += 3; + } + else if (inst == GOTO_W || inst == JSR_W) { + /* 4bytes offset */ + int offset = ByteArray.read32bit(code, i + 1); + offset = newOffset(i, offset, where, gapLength, exclusive); + newcode[j++] = code[i]; + ByteArray.write32bit(offset, newcode, j); + j += 4; + } + else if (inst == TABLESWITCH) { + if (i != j && (gapLength & 3) != 0) + throw new AlignmentException(); + + int i2 = (i & ~3) + 4; // 0-3 byte padding + // IBM JVM 1.4.2 cannot run the following code: + // int i0 = i; + // while (i0 < i2) + // newcode[j++] = code[i0++]; + // So extracting this code into an external method. + // see JIRA JASSIST-74. + j = copyGapBytes(newcode, j, code, i, i2); + + int defaultbyte = newOffset(i, ByteArray.read32bit(code, i2), + where, gapLength, exclusive); + ByteArray.write32bit(defaultbyte, newcode, j); + int lowbyte = ByteArray.read32bit(code, i2 + 4); + ByteArray.write32bit(lowbyte, newcode, j + 4); + int highbyte = ByteArray.read32bit(code, i2 + 8); + ByteArray.write32bit(highbyte, newcode, j + 8); + j += 12; + int i0 = i2 + 12; + i2 = i0 + (highbyte - lowbyte + 1) * 4; + while (i0 < i2) { + int offset = newOffset(i, ByteArray.read32bit(code, i0), + where, gapLength, exclusive); + ByteArray.write32bit(offset, newcode, j); + j += 4; + i0 += 4; + } + } + else if (inst == LOOKUPSWITCH) { + if (i != j && (gapLength & 3) != 0) + throw new AlignmentException(); + + int i2 = (i & ~3) + 4; // 0-3 byte padding + + // IBM JVM 1.4.2 cannot run the following code: + // int i0 = i; + // while (i0 < i2) + // newcode[j++] = code[i0++]; + // So extracting this code into an external method. + // see JIRA JASSIST-74. + j = copyGapBytes(newcode, j, code, i, i2); + + int defaultbyte = newOffset(i, ByteArray.read32bit(code, i2), + where, gapLength, exclusive); + ByteArray.write32bit(defaultbyte, newcode, j); + int npairs = ByteArray.read32bit(code, i2 + 4); + ByteArray.write32bit(npairs, newcode, j + 4); + j += 8; + int i0 = i2 + 8; + i2 = i0 + npairs * 8; + while (i0 < i2) { + ByteArray.copy32bit(code, i0, newcode, j); + int offset = newOffset(i, + ByteArray.read32bit(code, i0 + 4), + where, gapLength, exclusive); + ByteArray.write32bit(offset, newcode, j + 4); + j += 8; + i0 += 8; + } + } + else + while (i < nextPos) + newcode[j++] = code[i++]; + } + } + + + private static int copyGapBytes(byte[] newcode, int j, byte[] code, int i, int iEnd) { + switch (iEnd - i) { + case 4: + newcode[j++] = code[i++]; + case 3: + newcode[j++] = code[i++]; + case 2: + newcode[j++] = code[i++]; + case 1: + newcode[j++] = code[i++]; + default: + } + + return j; + } + + private static int newOffset(int i, int offset, int where, + int gapLength, boolean exclusive) { + int target = i + offset; + if (i < where) { + if (where < target || (exclusive && where == target)) + offset += gapLength; + } + else if (i == where) { + // This code is different from the code in Branch#shiftOffset(). + // see JASSIST-124. + if (target < where) + offset -= gapLength; + } + else + if (target < where || (!exclusive && where == target)) + offset -= gapLength; + + return offset; + } + + static class Pointers { + int cursor; + int mark0, mark; + ExceptionTable etable; + LineNumberAttribute line; + LocalVariableAttribute vars, types; + StackMapTable stack; + StackMap stack2; + + Pointers(int cur, int m, int m0, ExceptionTable et, CodeAttribute ca) { + cursor = cur; + mark = m; + mark0 = m0; + etable = et; // non null + line = (LineNumberAttribute)ca.getAttribute(LineNumberAttribute.tag); + vars = (LocalVariableAttribute)ca.getAttribute(LocalVariableAttribute.tag); + types = (LocalVariableAttribute)ca.getAttribute(LocalVariableAttribute.typeTag); + stack = (StackMapTable)ca.getAttribute(StackMapTable.tag); + stack2 = (StackMap)ca.getAttribute(StackMap.tag); + } + + void shiftPc(int where, int gapLength, boolean exclusive) throws BadBytecode { + if (where < cursor || (where == cursor && exclusive)) + cursor += gapLength; + + if (where < mark || (where == mark && exclusive)) + mark += gapLength; + + if (where < mark0 || (where == mark0 && exclusive)) + mark0 += gapLength; + + etable.shiftPc(where, gapLength, exclusive); + if (line != null) + line.shiftPc(where, gapLength, exclusive); + + if (vars != null) + vars.shiftPc(where, gapLength, exclusive); + + if (types != null) + types.shiftPc(where, gapLength, exclusive); + + if (stack != null) + stack.shiftPc(where, gapLength, exclusive); + + if (stack2 != null) + stack2.shiftPc(where, gapLength, exclusive); + } + } + + /* + * This method is called from CodeAttribute.LdcEntry.doit(). + */ + static byte[] changeLdcToLdcW(byte[] code, ExceptionTable etable, + CodeAttribute ca, CodeAttribute.LdcEntry ldcs) + throws BadBytecode + { + ArrayList jumps = makeJumpList(code, code.length); + while (ldcs != null) { + addLdcW(ldcs, jumps); + ldcs = ldcs.next; + } + + Pointers pointers = new Pointers(0, 0, 0, etable, ca); + byte[] r = insertGap2w(code, 0, 0, false, jumps, pointers); + return r; + } + + private static void addLdcW(CodeAttribute.LdcEntry ldcs, ArrayList jumps) { + int where = ldcs.where; + LdcW ldcw = new LdcW(where, ldcs.index); + int s = jumps.size(); + for (int i = 0; i < s; i++) + if (where < ((Branch)jumps.get(i)).orgPos) { + jumps.add(i, ldcw); + return; + } + + jumps.add(ldcw); + } + + /* + * insertGapCore0w() can handle a long code sequence more than 32K. + * It guarantees that the length of the inserted gap (NOPs) is equal to + * gapLength. No other NOPs except some NOPs following TABLESWITCH or + * LOOKUPSWITCH will not be inserted. + * + * Note: currentPos might be moved. + * + * @param where It must indicate the first byte of an opcode. + * @param newWhere It contains the updated index of the position where a gap + * is inserted and the length of the gap. + * It must not be null. + */ + private byte[] insertGapCore0w(byte[] code, int where, int gapLength, boolean exclusive, + ExceptionTable etable, CodeAttribute ca, Gap newWhere) + throws BadBytecode + { + if (gapLength <= 0) + return code; + + ArrayList jumps = makeJumpList(code, code.length); + Pointers pointers = new Pointers(currentPos, mark, where, etable, ca); + byte[] r = insertGap2w(code, where, gapLength, exclusive, jumps, pointers); + currentPos = pointers.cursor; + mark = pointers.mark; + int where2 = pointers.mark0; + if (where2 == currentPos && !exclusive) + currentPos += gapLength; + + if (exclusive) + where2 -= gapLength; + + newWhere.position = where2; + newWhere.length = gapLength; + return r; + } + + private static byte[] insertGap2w(byte[] code, int where, int gapLength, + boolean exclusive, ArrayList jumps, Pointers ptrs) + throws BadBytecode + { + int n = jumps.size(); + if (gapLength > 0) { + ptrs.shiftPc(where, gapLength, exclusive); + for (int i = 0; i < n; i++) + ((Branch)jumps.get(i)).shift(where, gapLength, exclusive); + } + + boolean unstable = true; + do { + while (unstable) { + unstable = false; + for (int i = 0; i < n; i++) { + Branch b = (Branch)jumps.get(i); + if (b.expanded()) { + unstable = true; + int p = b.pos; + int delta = b.deltaSize(); + ptrs.shiftPc(p, delta, false); + for (int j = 0; j < n; j++) + ((Branch)jumps.get(j)).shift(p, delta, false); + } + } + } + + for (int i = 0; i < n; i++) { + Branch b = (Branch)jumps.get(i); + int diff = b.gapChanged(); + if (diff > 0) { + unstable = true; + int p = b.pos; + ptrs.shiftPc(p, diff, false); + for (int j = 0; j < n; j++) + ((Branch)jumps.get(j)).shift(p, diff, false); + } + } + } while (unstable); + + return makeExapndedCode(code, jumps, where, gapLength); + } + + private static ArrayList makeJumpList(byte[] code, int endPos) + throws BadBytecode + { + ArrayList jumps = new ArrayList(); + int nextPos; + for (int i = 0; i < endPos; i = nextPos) { + nextPos = nextOpcode(code, i); + int inst = code[i] & 0xff; + // if<cond>, if_icmp<cond>, if_acmp<cond>, goto, jsr + if ((153 <= inst && inst <= 168) + || inst == IFNULL || inst == IFNONNULL) { + /* 2bytes *signed* offset */ + int offset = (code[i + 1] << 8) | (code[i + 2] & 0xff); + Branch b; + if (inst == GOTO || inst == JSR) + b = new Jump16(i, offset); + else + b = new If16(i, offset); + + jumps.add(b); + } + else if (inst == GOTO_W || inst == JSR_W) { + /* 4bytes offset */ + int offset = ByteArray.read32bit(code, i + 1); + jumps.add(new Jump32(i, offset)); + } + else if (inst == TABLESWITCH) { + int i2 = (i & ~3) + 4; // 0-3 byte padding + int defaultbyte = ByteArray.read32bit(code, i2); + int lowbyte = ByteArray.read32bit(code, i2 + 4); + int highbyte = ByteArray.read32bit(code, i2 + 8); + int i0 = i2 + 12; + int size = highbyte - lowbyte + 1; + int[] offsets = new int[size]; + for (int j = 0; j < size; j++) { + offsets[j] = ByteArray.read32bit(code, i0); + i0 += 4; + } + + jumps.add(new Table(i, defaultbyte, lowbyte, highbyte, offsets)); + } + else if (inst == LOOKUPSWITCH) { + int i2 = (i & ~3) + 4; // 0-3 byte padding + int defaultbyte = ByteArray.read32bit(code, i2); + int npairs = ByteArray.read32bit(code, i2 + 4); + int i0 = i2 + 8; + int[] matches = new int[npairs]; + int[] offsets = new int[npairs]; + for (int j = 0; j < npairs; j++) { + matches[j] = ByteArray.read32bit(code, i0); + offsets[j] = ByteArray.read32bit(code, i0 + 4); + i0 += 8; + } + + jumps.add(new Lookup(i, defaultbyte, matches, offsets)); + } + } + + return jumps; + } + + private static byte[] makeExapndedCode(byte[] code, ArrayList jumps, + int where, int gapLength) + throws BadBytecode + { + int n = jumps.size(); + int size = code.length + gapLength; + for (int i = 0; i < n; i++) { + Branch b = (Branch)jumps.get(i); + size += b.deltaSize(); + } + + byte[] newcode = new byte[size]; + int src = 0, dest = 0, bindex = 0; + int len = code.length; + Branch b; + int bpos; + if (0 < n) { + b = (Branch)jumps.get(0); + bpos = b.orgPos; + } + else { + b = null; + bpos = len; // src will be never equal to bpos + } + + while (src < len) { + if (src == where) { + int pos2 = dest + gapLength; + while (dest < pos2) + newcode[dest++] = NOP; + } + + if (src != bpos) + newcode[dest++] = code[src++]; + else { + int s = b.write(src, code, dest, newcode); + src += s; + dest += s + b.deltaSize(); + if (++bindex < n) { + b = (Branch)jumps.get(bindex); + bpos = b.orgPos; + } + else { + b = null; + bpos = len; + } + } + } + + return newcode; + } + + static abstract class Branch { + int pos, orgPos; + Branch(int p) { pos = orgPos = p; } + void shift(int where, int gapLength, boolean exclusive) { + if (where < pos || (where == pos && exclusive)) + pos += gapLength; + } + + static int shiftOffset(int i, int offset, int where, + int gapLength, boolean exclusive) { + int target = i + offset; + if (i < where) { + if (where < target || (exclusive && where == target)) + offset += gapLength; + } + else if (i == where) { + // This code is different from the code in CodeIterator#newOffset(). + // see JASSIST-124. + if (target < where && exclusive) + offset -= gapLength; + else if (where < target && !exclusive) + offset += gapLength; + } + else + if (target < where || (!exclusive && where == target)) + offset -= gapLength; + + return offset; + } + + boolean expanded() { return false; } + int gapChanged() { return 0; } + int deltaSize() { return 0; } // newSize - oldSize + + // This returns the original instruction size. + abstract int write(int srcPos, byte[] code, int destPos, byte[] newcode); + } + + /* used by changeLdcToLdcW() and CodeAttribute.LdcEntry. + */ + static class LdcW extends Branch { + int index; + boolean state; + LdcW(int p, int i) { + super(p); + index = i; + state = true; + } + + boolean expanded() { + if (state) { + state = false; + return true; + } + else + return false; + } + + int deltaSize() { return 1; } + + int write(int srcPos, byte[] code, int destPos, byte[] newcode) { + newcode[destPos] = LDC_W; + ByteArray.write16bit(index, newcode, destPos + 1); + return 2; + } + } + + static abstract class Branch16 extends Branch { + int offset; + int state; + static final int BIT16 = 0; + static final int EXPAND = 1; + static final int BIT32 = 2; + + Branch16(int p, int off) { + super(p); + offset = off; + state = BIT16; + } + + void shift(int where, int gapLength, boolean exclusive) { + offset = shiftOffset(pos, offset, where, gapLength, exclusive); + super.shift(where, gapLength, exclusive); + if (state == BIT16) + if (offset < Short.MIN_VALUE || Short.MAX_VALUE < offset) + state = EXPAND; + } + + boolean expanded() { + if (state == EXPAND) { + state = BIT32; + return true; + } + else + return false; + } + + abstract int deltaSize(); + abstract void write32(int src, byte[] code, int dest, byte[] newcode); + + int write(int src, byte[] code, int dest, byte[] newcode) { + if (state == BIT32) + write32(src, code, dest, newcode); + else { + newcode[dest] = code[src]; + ByteArray.write16bit(offset, newcode, dest + 1); + } + + return 3; + } + } + + // GOTO or JSR + static class Jump16 extends Branch16 { + Jump16(int p, int off) { + super(p, off); + } + + int deltaSize() { + return state == BIT32 ? 2 : 0; + } + + void write32(int src, byte[] code, int dest, byte[] newcode) { + newcode[dest] = (byte)(((code[src] & 0xff) == GOTO) ? GOTO_W : JSR_W); + ByteArray.write32bit(offset, newcode, dest + 1); + } + } + + // if<cond>, if_icmp<cond>, or if_acmp<cond> + static class If16 extends Branch16 { + If16(int p, int off) { + super(p, off); + } + + int deltaSize() { + return state == BIT32 ? 5 : 0; + } + + void write32(int src, byte[] code, int dest, byte[] newcode) { + newcode[dest] = (byte)opcode(code[src] & 0xff); + newcode[dest + 1] = 0; + newcode[dest + 2] = 8; // branch_offset = 8 + newcode[dest + 3] = (byte)GOTO_W; + ByteArray.write32bit(offset - 3, newcode, dest + 4); + } + + int opcode(int op) { + if (op == IFNULL) + return IFNONNULL; + else if (op == IFNONNULL) + return IFNULL; + else { + if (((op - IFEQ) & 1) == 0) + return op + 1; + else + return op - 1; + } + } + } + + static class Jump32 extends Branch { + int offset; + + Jump32(int p, int off) { + super(p); + offset = off; + } + + void shift(int where, int gapLength, boolean exclusive) { + offset = shiftOffset(pos, offset, where, gapLength, exclusive); + super.shift(where, gapLength, exclusive); + } + + int write(int src, byte[] code, int dest, byte[] newcode) { + newcode[dest] = code[src]; + ByteArray.write32bit(offset, newcode, dest + 1); + return 5; + } + } + + static abstract class Switcher extends Branch { + int gap, defaultByte; + int[] offsets; + + Switcher(int pos, int defaultByte, int[] offsets) { + super(pos); + this.gap = 3 - (pos & 3); + this.defaultByte = defaultByte; + this.offsets = offsets; + } + + void shift(int where, int gapLength, boolean exclusive) { + int p = pos; + defaultByte = shiftOffset(p, defaultByte, where, gapLength, exclusive); + int num = offsets.length; + for (int i = 0; i < num; i++) + offsets[i] = shiftOffset(p, offsets[i], where, gapLength, exclusive); + + super.shift(where, gapLength, exclusive); + } + + int gapChanged() { + int newGap = 3 - (pos & 3); + if (newGap > gap) { + int diff = newGap - gap; + gap = newGap; + return diff; + } + + return 0; + } + + int deltaSize() { + return gap - (3 - (orgPos & 3)); + } + + int write(int src, byte[] code, int dest, byte[] newcode) { + int padding = 3 - (pos & 3); + int nops = gap - padding; + int bytecodeSize = 5 + (3 - (orgPos & 3)) + tableSize(); + adjustOffsets(bytecodeSize, nops); + newcode[dest++] = code[src]; + while (padding-- > 0) + newcode[dest++] = 0; + + ByteArray.write32bit(defaultByte, newcode, dest); + int size = write2(dest + 4, newcode); + dest += size + 4; + while (nops-- > 0) + newcode[dest++] = NOP; + + return 5 + (3 - (orgPos & 3)) + size; + } + + abstract int write2(int dest, byte[] newcode); + abstract int tableSize(); + + /* If the new bytecode size is shorter than the original, some NOPs + * are appended after this branch instruction (tableswitch or + * lookupswitch) to fill the gap. + * This method changes a branch offset to point to the first NOP + * if the offset originally points to the bytecode next to this + * branch instruction. Otherwise, the bytecode would contain + * dead code. It complicates the generation of StackMap and + * StackMapTable. + */ + void adjustOffsets(int size, int nops) { + if (defaultByte == size) + defaultByte -= nops; + + for (int i = 0; i < offsets.length; i++) + if (offsets[i] == size) + offsets[i] -= nops; + } + } + + static class Table extends Switcher { + int low, high; + + Table(int pos, int defaultByte, int low, int high, int[] offsets) { + super(pos, defaultByte, offsets); + this.low = low; + this.high = high; + } + + int write2(int dest, byte[] newcode) { + ByteArray.write32bit(low, newcode, dest); + ByteArray.write32bit(high, newcode, dest + 4); + int n = offsets.length; + dest += 8; + for (int i = 0; i < n; i++) { + ByteArray.write32bit(offsets[i], newcode, dest); + dest += 4; + } + + return 8 + 4 * n; + } + + int tableSize() { return 8 + 4 * offsets.length; } + } + + static class Lookup extends Switcher { + int[] matches; + + Lookup(int pos, int defaultByte, int[] matches, int[] offsets) { + super(pos, defaultByte, offsets); + this.matches = matches; + } + + int write2(int dest, byte[] newcode) { + int n = matches.length; + ByteArray.write32bit(n, newcode, dest); + dest += 4; + for (int i = 0; i < n; i++) { + ByteArray.write32bit(matches[i], newcode, dest); + ByteArray.write32bit(offsets[i], newcode, dest + 4); + dest += 8; + } + + return 4 + 8 * n; + } + + int tableSize() { return 4 + 8 * matches.length; } + } +} diff --git a/src/main/javassist/bytecode/ConstPool.java b/src/main/javassist/bytecode/ConstPool.java new file mode 100644 index 0000000..df4e542 --- /dev/null +++ b/src/main/javassist/bytecode/ConstPool.java @@ -0,0 +1,1572 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.bytecode; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.ByteArrayOutputStream; +import java.io.PrintWriter; +import java.io.IOException; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import javassist.CtClass; + +/** + * Constant pool table. + */ +public final class ConstPool { + LongVector items; + int numOfItems; + HashMap classes; + HashMap strings; + ConstInfo[] constInfoCache; + int[] constInfoIndexCache; + int thisClassInfo; + + private static final int CACHE_SIZE = 32; + + /** + * A hash function for CACHE_SIZE + */ + private static int hashFunc(int a, int b) { + int h = -2128831035; + final int prime = 16777619; + h = (h ^ (a & 0xff)) * prime; + h = (h ^ (b & 0xff)) * prime; + + // changing the hash key size from 32bit to 5bit + h = (h >> 5) ^ (h & 0x1f); + return h & 0x1f; // 0..31 + } + + /** + * <code>CONSTANT_Class</code> + */ + public static final int CONST_Class = ClassInfo.tag; + + /** + * <code>CONSTANT_Fieldref</code> + */ + public static final int CONST_Fieldref = FieldrefInfo.tag; + + /** + * <code>CONSTANT_Methodref</code> + */ + public static final int CONST_Methodref = MethodrefInfo.tag; + + /** + * <code>CONSTANT_InterfaceMethodref</code> + */ + public static final int CONST_InterfaceMethodref + = InterfaceMethodrefInfo.tag; + + /** + * <code>CONSTANT_String</code> + */ + public static final int CONST_String = StringInfo.tag; + + /** + * <code>CONSTANT_Integer</code> + */ + public static final int CONST_Integer = IntegerInfo.tag; + + /** + * <code>CONSTANT_Float</code> + */ + public static final int CONST_Float = FloatInfo.tag; + + /** + * <code>CONSTANT_Long</code> + */ + public static final int CONST_Long = LongInfo.tag; + + /** + * <code>CONSTANT_Double</code> + */ + public static final int CONST_Double = DoubleInfo.tag; + + /** + * <code>CONSTANT_NameAndType</code> + */ + public static final int CONST_NameAndType = NameAndTypeInfo.tag; + + /** + * <code>CONSTANT_Utf8</code> + */ + public static final int CONST_Utf8 = Utf8Info.tag; + + /** + * Represents the class using this constant pool table. + */ + public static final CtClass THIS = null; + + /** + * Constructs a constant pool table. + * + * @param thisclass the name of the class using this constant + * pool table + */ + public ConstPool(String thisclass) { + items = new LongVector(); + numOfItems = 0; + addItem(null); // index 0 is reserved by the JVM. + classes = new HashMap(); + strings = new HashMap(); + constInfoCache = new ConstInfo[CACHE_SIZE]; + constInfoIndexCache = new int[CACHE_SIZE]; + thisClassInfo = addClassInfo(thisclass); + } + + /** + * Constructs a constant pool table from the given byte stream. + * + * @param in byte stream. + */ + public ConstPool(DataInputStream in) throws IOException { + classes = new HashMap(); + strings = new HashMap(); + constInfoCache = new ConstInfo[CACHE_SIZE]; + constInfoIndexCache = new int[CACHE_SIZE]; + thisClassInfo = 0; + /* read() initializes items and numOfItems, and do addItem(null). + */ + read(in); + } + + void prune() { + classes = new HashMap(); + strings = new HashMap(); + constInfoCache = new ConstInfo[CACHE_SIZE]; + constInfoIndexCache = new int[CACHE_SIZE]; + } + + /** + * Returns the number of entries in this table. + */ + public int getSize() { + return numOfItems; + } + + /** + * Returns the name of the class using this constant pool table. + */ + public String getClassName() { + return getClassInfo(thisClassInfo); + } + + /** + * Returns the index of <code>CONSTANT_Class_info</code> structure + * specifying the class using this constant pool table. + */ + public int getThisClassInfo() { + return thisClassInfo; + } + + void setThisClassInfo(int i) { + thisClassInfo = i; + } + + ConstInfo getItem(int n) { + return items.elementAt(n); + } + + /** + * Returns the <code>tag</code> field of the constant pool table + * entry at the given index. + */ + public int getTag(int index) { + return getItem(index).getTag(); + } + + /** + * Reads <code>CONSTANT_Class_info</code> structure + * at the given index. + * + * @return a fully-qualified class or interface name specified + * by <code>name_index</code>. If the type is an array + * type, this method returns an encoded name like + * <code>[java.lang.Object;</code> (note that the separators + * are not slashes but dots). + * @see javassist.ClassPool#getCtClass(String) + */ + public String getClassInfo(int index) { + ClassInfo c = (ClassInfo)getItem(index); + if (c == null) + return null; + else + return Descriptor.toJavaName(getUtf8Info(c.name)); + } + + /** + * Reads the <code>name_index</code> field of the + * <code>CONSTANT_NameAndType_info</code> structure + * at the given index. + */ + public int getNameAndTypeName(int index) { + NameAndTypeInfo ntinfo = (NameAndTypeInfo)getItem(index); + return ntinfo.memberName; + } + + /** + * Reads the <code>descriptor_index</code> field of the + * <code>CONSTANT_NameAndType_info</code> structure + * at the given index. + */ + public int getNameAndTypeDescriptor(int index) { + NameAndTypeInfo ntinfo = (NameAndTypeInfo)getItem(index); + return ntinfo.typeDescriptor; + } + + /** + * Reads the <code>class_index</code> field of the + * <code>CONSTANT_Fieldref_info</code>, + * <code>CONSTANT_Methodref_info</code>, + * or <code>CONSTANT_Interfaceref_info</code>, + * structure at the given index. + * + * @since 3.6 + */ + public int getMemberClass(int index) { + MemberrefInfo minfo = (MemberrefInfo)getItem(index); + return minfo.classIndex; + } + + /** + * Reads the <code>name_and_type_index</code> field of the + * <code>CONSTANT_Fieldref_info</code>, + * <code>CONSTANT_Methodref_info</code>, + * or <code>CONSTANT_Interfaceref_info</code>, + * structure at the given index. + * + * @since 3.6 + */ + public int getMemberNameAndType(int index) { + MemberrefInfo minfo = (MemberrefInfo)getItem(index); + return minfo.nameAndTypeIndex; + } + + /** + * Reads the <code>class_index</code> field of the + * <code>CONSTANT_Fieldref_info</code> structure + * at the given index. + */ + public int getFieldrefClass(int index) { + FieldrefInfo finfo = (FieldrefInfo)getItem(index); + return finfo.classIndex; + } + + /** + * Reads the <code>class_index</code> field of the + * <code>CONSTANT_Fieldref_info</code> structure + * at the given index. + * + * @return the name of the class at that <code>class_index</code>. + */ + public String getFieldrefClassName(int index) { + FieldrefInfo f = (FieldrefInfo)getItem(index); + if (f == null) + return null; + else + return getClassInfo(f.classIndex); + } + + /** + * Reads the <code>name_and_type_index</code> field of the + * <code>CONSTANT_Fieldref_info</code> structure + * at the given index. + */ + public int getFieldrefNameAndType(int index) { + FieldrefInfo finfo = (FieldrefInfo)getItem(index); + return finfo.nameAndTypeIndex; + } + + /** + * Reads the <code>name_index</code> field of the + * <code>CONSTANT_NameAndType_info</code> structure + * indirectly specified by the given index. + * + * @param index an index to a <code>CONSTANT_Fieldref_info</code>. + * @return the name of the field. + */ + public String getFieldrefName(int index) { + FieldrefInfo f = (FieldrefInfo)getItem(index); + if (f == null) + return null; + else { + NameAndTypeInfo n = (NameAndTypeInfo)getItem(f.nameAndTypeIndex); + if(n == null) + return null; + else + return getUtf8Info(n.memberName); + } + } + + /** + * Reads the <code>descriptor_index</code> field of the + * <code>CONSTANT_NameAndType_info</code> structure + * indirectly specified by the given index. + * + * @param index an index to a <code>CONSTANT_Fieldref_info</code>. + * @return the type descriptor of the field. + */ + public String getFieldrefType(int index) { + FieldrefInfo f = (FieldrefInfo)getItem(index); + if (f == null) + return null; + else { + NameAndTypeInfo n = (NameAndTypeInfo)getItem(f.nameAndTypeIndex); + if(n == null) + return null; + else + return getUtf8Info(n.typeDescriptor); + } + } + + /** + * Reads the <code>class_index</code> field of the + * <code>CONSTANT_Methodref_info</code> structure + * at the given index. + */ + public int getMethodrefClass(int index) { + MethodrefInfo minfo = (MethodrefInfo)getItem(index); + return minfo.classIndex; + } + + /** + * Reads the <code>class_index</code> field of the + * <code>CONSTANT_Methodref_info</code> structure + * at the given index. + * + * @return the name of the class at that <code>class_index</code>. + */ + public String getMethodrefClassName(int index) { + MethodrefInfo minfo = (MethodrefInfo)getItem(index); + if (minfo == null) + return null; + else + return getClassInfo(minfo.classIndex); + } + + /** + * Reads the <code>name_and_type_index</code> field of the + * <code>CONSTANT_Methodref_info</code> structure + * at the given index. + */ + public int getMethodrefNameAndType(int index) { + MethodrefInfo minfo = (MethodrefInfo)getItem(index); + return minfo.nameAndTypeIndex; + } + + /** + * Reads the <code>name_index</code> field of the + * <code>CONSTANT_NameAndType_info</code> structure + * indirectly specified by the given index. + * + * @param index an index to a <code>CONSTANT_Methodref_info</code>. + * @return the name of the method. + */ + public String getMethodrefName(int index) { + MethodrefInfo minfo = (MethodrefInfo)getItem(index); + if (minfo == null) + return null; + else { + NameAndTypeInfo n + = (NameAndTypeInfo)getItem(minfo.nameAndTypeIndex); + if(n == null) + return null; + else + return getUtf8Info(n.memberName); + } + } + + /** + * Reads the <code>descriptor_index</code> field of the + * <code>CONSTANT_NameAndType_info</code> structure + * indirectly specified by the given index. + * + * @param index an index to a <code>CONSTANT_Methodref_info</code>. + * @return the descriptor of the method. + */ + public String getMethodrefType(int index) { + MethodrefInfo minfo = (MethodrefInfo)getItem(index); + if (minfo == null) + return null; + else { + NameAndTypeInfo n + = (NameAndTypeInfo)getItem(minfo.nameAndTypeIndex); + if(n == null) + return null; + else + return getUtf8Info(n.typeDescriptor); + } + } + + /** + * Reads the <code>class_index</code> field of the + * <code>CONSTANT_InterfaceMethodref_info</code> structure + * at the given index. + */ + public int getInterfaceMethodrefClass(int index) { + InterfaceMethodrefInfo minfo + = (InterfaceMethodrefInfo)getItem(index); + return minfo.classIndex; + } + + /** + * Reads the <code>class_index</code> field of the + * <code>CONSTANT_InterfaceMethodref_info</code> structure + * at the given index. + * + * @return the name of the class at that <code>class_index</code>. + */ + public String getInterfaceMethodrefClassName(int index) { + InterfaceMethodrefInfo minfo + = (InterfaceMethodrefInfo)getItem(index); + return getClassInfo(minfo.classIndex); + } + + /** + * Reads the <code>name_and_type_index</code> field of the + * <code>CONSTANT_InterfaceMethodref_info</code> structure + * at the given index. + */ + public int getInterfaceMethodrefNameAndType(int index) { + InterfaceMethodrefInfo minfo + = (InterfaceMethodrefInfo)getItem(index); + return minfo.nameAndTypeIndex; + } + + /** + * Reads the <code>name_index</code> field of the + * <code>CONSTANT_NameAndType_info</code> structure + * indirectly specified by the given index. + * + * @param index an index to + * a <code>CONSTANT_InterfaceMethodref_info</code>. + * @return the name of the method. + */ + public String getInterfaceMethodrefName(int index) { + InterfaceMethodrefInfo minfo + = (InterfaceMethodrefInfo)getItem(index); + if (minfo == null) + return null; + else { + NameAndTypeInfo n + = (NameAndTypeInfo)getItem(minfo.nameAndTypeIndex); + if(n == null) + return null; + else + return getUtf8Info(n.memberName); + } + } + + /** + * Reads the <code>descriptor_index</code> field of the + * <code>CONSTANT_NameAndType_info</code> structure + * indirectly specified by the given index. + * + * @param index an index to + * a <code>CONSTANT_InterfaceMethodref_info</code>. + * @return the descriptor of the method. + */ + public String getInterfaceMethodrefType(int index) { + InterfaceMethodrefInfo minfo + = (InterfaceMethodrefInfo)getItem(index); + if (minfo == null) + return null; + else { + NameAndTypeInfo n + = (NameAndTypeInfo)getItem(minfo.nameAndTypeIndex); + if(n == null) + return null; + else + return getUtf8Info(n.typeDescriptor); + } + } + /** + * Reads <code>CONSTANT_Integer_info</code>, <code>_Float_info</code>, + * <code>_Long_info</code>, <code>_Double_info</code>, or + * <code>_String_info</code> structure. + * These are used with the LDC instruction. + * + * @return a <code>String</code> value or a wrapped primitive-type + * value. + */ + public Object getLdcValue(int index) { + ConstInfo constInfo = this.getItem(index); + Object value = null; + if (constInfo instanceof StringInfo) + value = this.getStringInfo(index); + else if (constInfo instanceof FloatInfo) + value = new Float(getFloatInfo(index)); + else if (constInfo instanceof IntegerInfo) + value = new Integer(getIntegerInfo(index)); + else if (constInfo instanceof LongInfo) + value = new Long(getLongInfo(index)); + else if (constInfo instanceof DoubleInfo) + value = new Double(getDoubleInfo(index)); + else + value = null; + + return value; + } + + /** + * Reads <code>CONSTANT_Integer_info</code> structure + * at the given index. + * + * @return the value specified by this entry. + */ + public int getIntegerInfo(int index) { + IntegerInfo i = (IntegerInfo)getItem(index); + return i.value; + } + + /** + * Reads <code>CONSTANT_Float_info</code> structure + * at the given index. + * + * @return the value specified by this entry. + */ + public float getFloatInfo(int index) { + FloatInfo i = (FloatInfo)getItem(index); + return i.value; + } + + /** + * Reads <code>CONSTANT_Long_info</code> structure + * at the given index. + * + * @return the value specified by this entry. + */ + public long getLongInfo(int index) { + LongInfo i = (LongInfo)getItem(index); + return i.value; + } + + /** + * Reads <code>CONSTANT_Double_info</code> structure + * at the given index. + * + * @return the value specified by this entry. + */ + public double getDoubleInfo(int index) { + DoubleInfo i = (DoubleInfo)getItem(index); + return i.value; + } + + /** + * Reads <code>CONSTANT_String_info</code> structure + * at the given index. + * + * @return the string specified by <code>string_index</code>. + */ + public String getStringInfo(int index) { + StringInfo si = (StringInfo)getItem(index); + return getUtf8Info(si.string); + } + + /** + * Reads <code>CONSTANT_utf8_info</code> structure + * at the given index. + * + * @return the string specified by this entry. + */ + public String getUtf8Info(int index) { + Utf8Info utf = (Utf8Info)getItem(index); + return utf.string; + } + + /** + * Determines whether <code>CONSTANT_Methodref_info</code> + * structure at the given index represents the constructor + * of the given class. + * + * @return the <code>descriptor_index</code> specifying + * the type descriptor of the that constructor. + * If it is not that constructor, + * <code>isConstructor()</code> returns 0. + */ + public int isConstructor(String classname, int index) { + return isMember(classname, MethodInfo.nameInit, index); + } + + /** + * Determines whether <code>CONSTANT_Methodref_info</code>, + * <code>CONSTANT_Fieldref_info</code>, or + * <code>CONSTANT_InterfaceMethodref_info</code> structure + * at the given index represents the member with the specified + * name and declaring class. + * + * @param classname the class declaring the member + * @param membername the member name + * @param index the index into the constant pool table + * + * @return the <code>descriptor_index</code> specifying + * the type descriptor of that member. + * If it is not that member, + * <code>isMember()</code> returns 0. + */ + public int isMember(String classname, String membername, int index) { + MemberrefInfo minfo = (MemberrefInfo)getItem(index); + if (getClassInfo(minfo.classIndex).equals(classname)) { + NameAndTypeInfo ntinfo + = (NameAndTypeInfo)getItem(minfo.nameAndTypeIndex); + if (getUtf8Info(ntinfo.memberName).equals(membername)) + return ntinfo.typeDescriptor; + } + + return 0; // false + } + + /** + * Determines whether <code>CONSTANT_Methodref_info</code>, + * <code>CONSTANT_Fieldref_info</code>, or + * <code>CONSTANT_InterfaceMethodref_info</code> structure + * at the given index has the name and the descriptor + * given as the arguments. + * + * @param membername the member name + * @param desc the descriptor of the member. + * @param index the index into the constant pool table + * + * @return the name of the target class specified by + * the <code>..._info</code> structure + * at <code>index</code>. + * Otherwise, null if that structure does not + * match the given member name and descriptor. + */ + public String eqMember(String membername, String desc, int index) { + MemberrefInfo minfo = (MemberrefInfo)getItem(index); + NameAndTypeInfo ntinfo + = (NameAndTypeInfo)getItem(minfo.nameAndTypeIndex); + if (getUtf8Info(ntinfo.memberName).equals(membername) + && getUtf8Info(ntinfo.typeDescriptor).equals(desc)) + return getClassInfo(minfo.classIndex); + else + return null; // false + } + + private int addItem(ConstInfo info) { + items.addElement(info); + return numOfItems++; + } + + /** + * Copies the n-th item in this ConstPool object into the destination + * ConstPool object. + * The class names that the item refers to are renamed according + * to the given map. + * + * @param n the <i>n</i>-th item + * @param dest destination constant pool table + * @param classnames the map or null. + * @return the index of the copied item into the destination ClassPool. + */ + public int copy(int n, ConstPool dest, Map classnames) { + if (n == 0) + return 0; + + ConstInfo info = getItem(n); + return info.copy(this, dest, classnames); + } + + int addConstInfoPadding() { + return addItem(new ConstInfoPadding()); + } + + /** + * Adds a new <code>CONSTANT_Class_info</code> structure. + * + * <p>This also adds a <code>CONSTANT_Utf8_info</code> structure + * for storing the class name. + * + * @return the index of the added entry. + */ + public int addClassInfo(CtClass c) { + if (c == THIS) + return thisClassInfo; + else if (!c.isArray()) + return addClassInfo(c.getName()); + else { + // an array type is recorded in the hashtable with + // the key "[L<classname>;" instead of "<classname>". + // + // note: toJvmName(toJvmName(c)) is equal to toJvmName(c). + + return addClassInfo(Descriptor.toJvmName(c)); + } + } + + /** + * Adds a new <code>CONSTANT_Class_info</code> structure. + * + * <p>This also adds a <code>CONSTANT_Utf8_info</code> structure + * for storing the class name. + * + * @param qname a fully-qualified class name + * (or the JVM-internal representation of that name). + * @return the index of the added entry. + */ + public int addClassInfo(String qname) { + ClassInfo info = (ClassInfo)classes.get(qname); + if (info != null) + return info.index; + else { + int utf8 = addUtf8Info(Descriptor.toJvmName(qname)); + info = new ClassInfo(utf8, numOfItems); + classes.put(qname, info); + return addItem(info); + } + } + + /** + * Adds a new <code>CONSTANT_NameAndType_info</code> structure. + * + * <p>This also adds <code>CONSTANT_Utf8_info</code> structures. + * + * @param name <code>name_index</code> + * @param type <code>descriptor_index</code> + * @return the index of the added entry. + */ + public int addNameAndTypeInfo(String name, String type) { + return addNameAndTypeInfo(addUtf8Info(name), addUtf8Info(type)); + } + + /** + * Adds a new <code>CONSTANT_NameAndType_info</code> structure. + * + * @param name <code>name_index</code> + * @param type <code>descriptor_index</code> + * @return the index of the added entry. + */ + public int addNameAndTypeInfo(int name, int type) { + int h = hashFunc(name, type); + ConstInfo ci = constInfoCache[h]; + if (ci != null && ci instanceof NameAndTypeInfo && ci.hashCheck(name, type)) + return constInfoIndexCache[h]; + else { + NameAndTypeInfo item = new NameAndTypeInfo(name, type); + constInfoCache[h] = item; + int i = addItem(item); + constInfoIndexCache[h] = i; + return i; + } + } + + /** + * Adds a new <code>CONSTANT_Fieldref_info</code> structure. + * + * <p>This also adds a new <code>CONSTANT_NameAndType_info</code> + * structure. + * + * @param classInfo <code>class_index</code> + * @param name <code>name_index</code> + * of <code>CONSTANT_NameAndType_info</code>. + * @param type <code>descriptor_index</code> + * of <code>CONSTANT_NameAndType_info</code>. + * @return the index of the added entry. + */ + public int addFieldrefInfo(int classInfo, String name, String type) { + int nt = addNameAndTypeInfo(name, type); + return addFieldrefInfo(classInfo, nt); + } + + /** + * Adds a new <code>CONSTANT_Fieldref_info</code> structure. + * + * @param classInfo <code>class_index</code> + * @param nameAndTypeInfo <code>name_and_type_index</code>. + * @return the index of the added entry. + */ + public int addFieldrefInfo(int classInfo, int nameAndTypeInfo) { + int h = hashFunc(classInfo, nameAndTypeInfo); + ConstInfo ci = constInfoCache[h]; + if (ci != null && ci instanceof FieldrefInfo && ci.hashCheck(classInfo, nameAndTypeInfo)) + return constInfoIndexCache[h]; + else { + FieldrefInfo item = new FieldrefInfo(classInfo, nameAndTypeInfo); + constInfoCache[h] = item; + int i = addItem(item); + constInfoIndexCache[h] = i; + return i; + } + } + + /** + * Adds a new <code>CONSTANT_Methodref_info</code> structure. + * + * <p>This also adds a new <code>CONSTANT_NameAndType_info</code> + * structure. + * + * @param classInfo <code>class_index</code> + * @param name <code>name_index</code> + * of <code>CONSTANT_NameAndType_info</code>. + * @param type <code>descriptor_index</code> + * of <code>CONSTANT_NameAndType_info</code>. + * @return the index of the added entry. + */ + public int addMethodrefInfo(int classInfo, String name, String type) { + int nt = addNameAndTypeInfo(name, type); + return addMethodrefInfo(classInfo, nt); + } + + /** + * Adds a new <code>CONSTANT_Methodref_info</code> structure. + * + * @param classInfo <code>class_index</code> + * @param nameAndTypeInfo <code>name_and_type_index</code>. + * @return the index of the added entry. + */ + public int addMethodrefInfo(int classInfo, int nameAndTypeInfo) { + int h = hashFunc(classInfo, nameAndTypeInfo); + ConstInfo ci = constInfoCache[h]; + if (ci != null && ci instanceof MethodrefInfo && ci.hashCheck(classInfo, nameAndTypeInfo)) + return constInfoIndexCache[h]; + else { + MethodrefInfo item = new MethodrefInfo(classInfo, nameAndTypeInfo); + constInfoCache[h] = item; + int i = addItem(item); + constInfoIndexCache[h] = i; + return i; + } + } + + /** + * Adds a new <code>CONSTANT_InterfaceMethodref_info</code> + * structure. + * + * <p>This also adds a new <code>CONSTANT_NameAndType_info</code> + * structure. + * + * @param classInfo <code>class_index</code> + * @param name <code>name_index</code> + * of <code>CONSTANT_NameAndType_info</code>. + * @param type <code>descriptor_index</code> + * of <code>CONSTANT_NameAndType_info</code>. + * @return the index of the added entry. + */ + public int addInterfaceMethodrefInfo(int classInfo, String name, + String type) { + int nt = addNameAndTypeInfo(name, type); + return addInterfaceMethodrefInfo(classInfo, nt); + } + + /** + * Adds a new <code>CONSTANT_InterfaceMethodref_info</code> + * structure. + * + * @param classInfo <code>class_index</code> + * @param nameAndTypeInfo <code>name_and_type_index</code>. + * @return the index of the added entry. + */ + public int addInterfaceMethodrefInfo(int classInfo, + int nameAndTypeInfo) { + int h = hashFunc(classInfo, nameAndTypeInfo); + ConstInfo ci = constInfoCache[h]; + if (ci != null && ci instanceof InterfaceMethodrefInfo && ci.hashCheck(classInfo, nameAndTypeInfo)) + return constInfoIndexCache[h]; + else { + InterfaceMethodrefInfo item =new InterfaceMethodrefInfo(classInfo, nameAndTypeInfo); + constInfoCache[h] = item; + int i = addItem(item); + constInfoIndexCache[h] = i; + return i; + } + } + + /** + * Adds a new <code>CONSTANT_String_info</code> + * structure. + * + * <p>This also adds a new <code>CONSTANT_Utf8_info</code> + * structure. + * + * @return the index of the added entry. + */ + public int addStringInfo(String str) { + return addItem(new StringInfo(addUtf8Info(str))); + } + + /** + * Adds a new <code>CONSTANT_Integer_info</code> + * structure. + * + * @return the index of the added entry. + */ + public int addIntegerInfo(int i) { + return addItem(new IntegerInfo(i)); + } + + /** + * Adds a new <code>CONSTANT_Float_info</code> + * structure. + * + * @return the index of the added entry. + */ + public int addFloatInfo(float f) { + return addItem(new FloatInfo(f)); + } + + /** + * Adds a new <code>CONSTANT_Long_info</code> + * structure. + * + * @return the index of the added entry. + */ + public int addLongInfo(long l) { + int i = addItem(new LongInfo(l)); + addItem(new ConstInfoPadding()); + return i; + } + + /** + * Adds a new <code>CONSTANT_Double_info</code> + * structure. + * + * @return the index of the added entry. + */ + public int addDoubleInfo(double d) { + int i = addItem(new DoubleInfo(d)); + addItem(new ConstInfoPadding()); + return i; + } + + /** + * Adds a new <code>CONSTANT_Utf8_info</code> + * structure. + * + * <p>If the given utf8 string has been already recorded in the + * table, then this method does not add a new entry to avoid adding + * a duplicated entry. + * Instead, it returns the index of the entry already recorded. + * + * @return the index of the added entry. + */ + public int addUtf8Info(String utf8) { + Utf8Info info = (Utf8Info)strings.get(utf8); + if (info != null) + return info.index; + else { + info = new Utf8Info(utf8, numOfItems); + strings.put(utf8, info); + return addItem(info); + } + } + + /** + * Get all the class names. + * + * @return a set of class names + */ + public Set getClassNames() + { + HashSet result = new HashSet(); + LongVector v = items; + int size = numOfItems; + for (int i = 1; i < size; ++i) { + String className = v.elementAt(i).getClassName(this); + if (className != null) + result.add(className); + } + return result; + } + + /** + * Replaces all occurrences of a class name. + * + * @param oldName the replaced name (JVM-internal representation). + * @param newName the substituted name (JVM-internal representation). + */ + public void renameClass(String oldName, String newName) { + LongVector v = items; + int size = numOfItems; + classes = new HashMap(classes.size() * 2); + for (int i = 1; i < size; ++i) { + ConstInfo ci = v.elementAt(i); + ci.renameClass(this, oldName, newName); + ci.makeHashtable(this); + } + } + + /** + * Replaces all occurrences of class names. + * + * @param classnames specifies pairs of replaced and substituted + * name. + */ + public void renameClass(Map classnames) { + LongVector v = items; + int size = numOfItems; + classes = new HashMap(classes.size() * 2); + for (int i = 1; i < size; ++i) { + ConstInfo ci = v.elementAt(i); + ci.renameClass(this, classnames); + ci.makeHashtable(this); + } + } + + private void read(DataInputStream in) throws IOException { + int n = in.readUnsignedShort(); + + items = new LongVector(n); + numOfItems = 0; + addItem(null); // index 0 is reserved by the JVM. + + while (--n > 0) { // index 0 is reserved by JVM + int tag = readOne(in); + if ((tag == LongInfo.tag) || (tag == DoubleInfo.tag)) { + addItem(new ConstInfoPadding()); + --n; + } + } + + int i = 1; + while (true) { + ConstInfo info = items.elementAt(i++); + if (info == null) + break; + else + info.makeHashtable(this); + } + } + + private int readOne(DataInputStream in) throws IOException { + ConstInfo info; + int tag = in.readUnsignedByte(); + switch (tag) { + case Utf8Info.tag : // 1 + info = new Utf8Info(in, numOfItems); + strings.put(((Utf8Info)info).string, info); + break; + case IntegerInfo.tag : // 3 + info = new IntegerInfo(in); + break; + case FloatInfo.tag : // 4 + info = new FloatInfo(in); + break; + case LongInfo.tag : // 5 + info = new LongInfo(in); + break; + case DoubleInfo.tag : // 6 + info = new DoubleInfo(in); + break; + case ClassInfo.tag : // 7 + info = new ClassInfo(in, numOfItems); + // classes.put(<classname>, info); + break; + case StringInfo.tag : // 8 + info = new StringInfo(in); + break; + case FieldrefInfo.tag : // 9 + info = new FieldrefInfo(in); + break; + case MethodrefInfo.tag : // 10 + info = new MethodrefInfo(in); + break; + case InterfaceMethodrefInfo.tag : // 11 + info = new InterfaceMethodrefInfo(in); + break; + case NameAndTypeInfo.tag : // 12 + info = new NameAndTypeInfo(in); + break; + default : + throw new IOException("invalid constant type: " + tag); + } + + addItem(info); + return tag; + } + + /** + * Writes the contents of the constant pool table. + */ + public void write(DataOutputStream out) throws IOException { + out.writeShort(numOfItems); + LongVector v = items; + int size = numOfItems; + for (int i = 1; i < size; ++i) + v.elementAt(i).write(out); + } + + /** + * Prints the contents of the constant pool table. + */ + public void print() { + print(new PrintWriter(System.out, true)); + } + + /** + * Prints the contents of the constant pool table. + */ + public void print(PrintWriter out) { + int size = numOfItems; + for (int i = 1; i < size; ++i) { + out.print(i); + out.print(" "); + items.elementAt(i).print(out); + } + } +} + +abstract class ConstInfo { + public abstract int getTag(); + + public String getClassName(ConstPool cp) { return null; } + public void renameClass(ConstPool cp, String oldName, String newName) {} + public void renameClass(ConstPool cp, Map classnames) {} + public abstract int copy(ConstPool src, ConstPool dest, Map classnames); + // ** classnames is a mapping between JVM names. + + public abstract void write(DataOutputStream out) throws IOException; + public abstract void print(PrintWriter out); + + void makeHashtable(ConstPool cp) {} // called after read() finishes in ConstPool. + + boolean hashCheck(int a, int b) { return false; } + + public String toString() { + ByteArrayOutputStream bout = new ByteArrayOutputStream(); + PrintWriter out = new PrintWriter(bout); + print(out); + return bout.toString(); + } +} + +/* padding following DoubleInfo or LongInfo. + */ +class ConstInfoPadding extends ConstInfo { + public int getTag() { return 0; } + + public int copy(ConstPool src, ConstPool dest, Map map) { + return dest.addConstInfoPadding(); + } + + public void write(DataOutputStream out) throws IOException {} + + public void print(PrintWriter out) { + out.println("padding"); + } +} + +class ClassInfo extends ConstInfo { + static final int tag = 7; + int name; + int index; + + public ClassInfo(int className, int i) { + name = className; + index = i; + } + + public ClassInfo(DataInputStream in, int i) throws IOException { + name = in.readUnsignedShort(); + index = i; + } + + public int getTag() { return tag; } + + public String getClassName(ConstPool cp) { + return cp.getUtf8Info(name); + }; + + public void renameClass(ConstPool cp, String oldName, String newName) { + String nameStr = cp.getUtf8Info(name); + if (nameStr.equals(oldName)) + name = cp.addUtf8Info(newName); + else if (nameStr.charAt(0) == '[') { + String nameStr2 = Descriptor.rename(nameStr, oldName, newName); + if (nameStr != nameStr2) + name = cp.addUtf8Info(nameStr2); + } + } + + public void renameClass(ConstPool cp, Map map) { + String oldName = cp.getUtf8Info(name); + if (oldName.charAt(0) == '[') { + String newName = Descriptor.rename(oldName, map); + if (oldName != newName) + name = cp.addUtf8Info(newName); + } + else { + String newName = (String)map.get(oldName); + if (newName != null && !newName.equals(oldName)) + name = cp.addUtf8Info(newName); + } + } + + public int copy(ConstPool src, ConstPool dest, Map map) { + String classname = src.getUtf8Info(name); + if (map != null) { + String newname = (String)map.get(classname); + if (newname != null) + classname = newname; + } + + return dest.addClassInfo(classname); + } + + public void write(DataOutputStream out) throws IOException { + out.writeByte(tag); + out.writeShort(name); + } + + public void print(PrintWriter out) { + out.print("Class #"); + out.println(name); + } + + void makeHashtable(ConstPool cp) { + String name = Descriptor.toJavaName(getClassName(cp)); + cp.classes.put(name, this); + } +} + +class NameAndTypeInfo extends ConstInfo { + static final int tag = 12; + int memberName; + int typeDescriptor; + + public NameAndTypeInfo(int name, int type) { + memberName = name; + typeDescriptor = type; + } + + public NameAndTypeInfo(DataInputStream in) throws IOException { + memberName = in.readUnsignedShort(); + typeDescriptor = in.readUnsignedShort(); + } + + boolean hashCheck(int a, int b) { return a == memberName && b == typeDescriptor; } + + public int getTag() { return tag; } + + public void renameClass(ConstPool cp, String oldName, String newName) { + String type = cp.getUtf8Info(typeDescriptor); + String type2 = Descriptor.rename(type, oldName, newName); + if (type != type2) + typeDescriptor = cp.addUtf8Info(type2); + } + + public void renameClass(ConstPool cp, Map map) { + String type = cp.getUtf8Info(typeDescriptor); + String type2 = Descriptor.rename(type, map); + if (type != type2) + typeDescriptor = cp.addUtf8Info(type2); + } + + public int copy(ConstPool src, ConstPool dest, Map map) { + String mname = src.getUtf8Info(memberName); + String tdesc = src.getUtf8Info(typeDescriptor); + tdesc = Descriptor.rename(tdesc, map); + return dest.addNameAndTypeInfo(dest.addUtf8Info(mname), + dest.addUtf8Info(tdesc)); + } + + public void write(DataOutputStream out) throws IOException { + out.writeByte(tag); + out.writeShort(memberName); + out.writeShort(typeDescriptor); + } + + public void print(PrintWriter out) { + out.print("NameAndType #"); + out.print(memberName); + out.print(", type #"); + out.println(typeDescriptor); + } +} + +abstract class MemberrefInfo extends ConstInfo { + int classIndex; + int nameAndTypeIndex; + + public MemberrefInfo(int cindex, int ntindex) { + classIndex = cindex; + nameAndTypeIndex = ntindex; + } + + public MemberrefInfo(DataInputStream in) throws IOException { + classIndex = in.readUnsignedShort(); + nameAndTypeIndex = in.readUnsignedShort(); + } + + public int copy(ConstPool src, ConstPool dest, Map map) { + int classIndex2 = src.getItem(classIndex).copy(src, dest, map); + int ntIndex2 = src.getItem(nameAndTypeIndex).copy(src, dest, map); + return copy2(dest, classIndex2, ntIndex2); + } + + boolean hashCheck(int a, int b) { return a == classIndex && b == nameAndTypeIndex; } + + abstract protected int copy2(ConstPool dest, int cindex, int ntindex); + + public void write(DataOutputStream out) throws IOException { + out.writeByte(getTag()); + out.writeShort(classIndex); + out.writeShort(nameAndTypeIndex); + } + + public void print(PrintWriter out) { + out.print(getTagName() + " #"); + out.print(classIndex); + out.print(", name&type #"); + out.println(nameAndTypeIndex); + } + + public abstract String getTagName(); +} + +class FieldrefInfo extends MemberrefInfo { + static final int tag = 9; + + public FieldrefInfo(int cindex, int ntindex) { + super(cindex, ntindex); + } + + public FieldrefInfo(DataInputStream in) throws IOException { + super(in); + } + + public int getTag() { return tag; } + + public String getTagName() { return "Field"; } + + protected int copy2(ConstPool dest, int cindex, int ntindex) { + return dest.addFieldrefInfo(cindex, ntindex); + } +} + +class MethodrefInfo extends MemberrefInfo { + static final int tag = 10; + + public MethodrefInfo(int cindex, int ntindex) { + super(cindex, ntindex); + } + + public MethodrefInfo(DataInputStream in) throws IOException { + super(in); + } + + public int getTag() { return tag; } + + public String getTagName() { return "Method"; } + + protected int copy2(ConstPool dest, int cindex, int ntindex) { + return dest.addMethodrefInfo(cindex, ntindex); + } +} + +class InterfaceMethodrefInfo extends MemberrefInfo { + static final int tag = 11; + + public InterfaceMethodrefInfo(int cindex, int ntindex) { + super(cindex, ntindex); + } + + public InterfaceMethodrefInfo(DataInputStream in) throws IOException { + super(in); + } + + public int getTag() { return tag; } + + public String getTagName() { return "Interface"; } + + protected int copy2(ConstPool dest, int cindex, int ntindex) { + return dest.addInterfaceMethodrefInfo(cindex, ntindex); + } +} + +class StringInfo extends ConstInfo { + static final int tag = 8; + int string; + + public StringInfo(int str) { + string = str; + } + + public StringInfo(DataInputStream in) throws IOException { + string = in.readUnsignedShort(); + } + + public int getTag() { return tag; } + + public int copy(ConstPool src, ConstPool dest, Map map) { + return dest.addStringInfo(src.getUtf8Info(string)); + } + + public void write(DataOutputStream out) throws IOException { + out.writeByte(tag); + out.writeShort(string); + } + + public void print(PrintWriter out) { + out.print("String #"); + out.println(string); + } +} + +class IntegerInfo extends ConstInfo { + static final int tag = 3; + int value; + + public IntegerInfo(int i) { + value = i; + } + + public IntegerInfo(DataInputStream in) throws IOException { + value = in.readInt(); + } + + public int getTag() { return tag; } + + public int copy(ConstPool src, ConstPool dest, Map map) { + return dest.addIntegerInfo(value); + } + + public void write(DataOutputStream out) throws IOException { + out.writeByte(tag); + out.writeInt(value); + } + + public void print(PrintWriter out) { + out.print("Integer "); + out.println(value); + } +} + +class FloatInfo extends ConstInfo { + static final int tag = 4; + float value; + + public FloatInfo(float f) { + value = f; + } + + public FloatInfo(DataInputStream in) throws IOException { + value = in.readFloat(); + } + + public int getTag() { return tag; } + + public int copy(ConstPool src, ConstPool dest, Map map) { + return dest.addFloatInfo(value); + } + + public void write(DataOutputStream out) throws IOException { + out.writeByte(tag); + out.writeFloat(value); + } + + public void print(PrintWriter out) { + out.print("Float "); + out.println(value); + } +} + +class LongInfo extends ConstInfo { + static final int tag = 5; + long value; + + public LongInfo(long l) { + value = l; + } + + public LongInfo(DataInputStream in) throws IOException { + value = in.readLong(); + } + + public int getTag() { return tag; } + + public int copy(ConstPool src, ConstPool dest, Map map) { + return dest.addLongInfo(value); + } + + public void write(DataOutputStream out) throws IOException { + out.writeByte(tag); + out.writeLong(value); + } + + public void print(PrintWriter out) { + out.print("Long "); + out.println(value); + } +} + +class DoubleInfo extends ConstInfo { + static final int tag = 6; + double value; + + public DoubleInfo(double d) { + value = d; + } + + public DoubleInfo(DataInputStream in) throws IOException { + value = in.readDouble(); + } + + public int getTag() { return tag; } + + public int copy(ConstPool src, ConstPool dest, Map map) { + return dest.addDoubleInfo(value); + } + + public void write(DataOutputStream out) throws IOException { + out.writeByte(tag); + out.writeDouble(value); + } + + public void print(PrintWriter out) { + out.print("Double "); + out.println(value); + } +} + +class Utf8Info extends ConstInfo { + static final int tag = 1; + String string; + int index; + + public Utf8Info(String utf8, int i) { + string = utf8; + index = i; + } + + public Utf8Info(DataInputStream in, int i) throws IOException { + string = in.readUTF(); + index = i; + } + + public int getTag() { return tag; } + + public int copy(ConstPool src, ConstPool dest, Map map) { + return dest.addUtf8Info(string); + } + + public void write(DataOutputStream out) throws IOException { + out.writeByte(tag); + out.writeUTF(string); + } + + public void print(PrintWriter out) { + out.print("UTF8 \""); + out.print(string); + out.println("\""); + } +} diff --git a/src/main/javassist/bytecode/ConstantAttribute.java b/src/main/javassist/bytecode/ConstantAttribute.java new file mode 100644 index 0000000..47f993b --- /dev/null +++ b/src/main/javassist/bytecode/ConstantAttribute.java @@ -0,0 +1,72 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.bytecode; + +import java.io.DataInputStream; +import java.util.Map; +import java.io.IOException; + +/** + * <code>ConstantValue_attribute</code>. + */ +public class ConstantAttribute extends AttributeInfo { + /** + * The name of this attribute <code>"ConstantValue"</code>. + */ + public static final String tag = "ConstantValue"; + + ConstantAttribute(ConstPool cp, int n, DataInputStream in) + throws IOException + { + super(cp, n, in); + } + + /** + * Constructs a ConstantValue attribute. + * + * @param cp a constant pool table. + * @param index <code>constantvalue_index</code> + * of <code>ConstantValue_attribute</code>. + */ + public ConstantAttribute(ConstPool cp, int index) { + super(cp, tag); + byte[] bvalue = new byte[2]; + bvalue[0] = (byte)(index >>> 8); + bvalue[1] = (byte)index; + set(bvalue); + } + + /** + * Returns <code>constantvalue_index</code>. + */ + public int getConstantValue() { + return ByteArray.readU16bit(get(), 0); + } + + /** + * Makes a copy. Class names are replaced according to the + * given <code>Map</code> object. + * + * @param newCp the constant pool table used by the new copy. + * @param classnames pairs of replaced and substituted + * class names. + */ + public AttributeInfo copy(ConstPool newCp, Map classnames) { + int index = getConstPool().copy(getConstantValue(), newCp, + classnames); + return new ConstantAttribute(newCp, index); + } +} diff --git a/src/main/javassist/bytecode/DeprecatedAttribute.java b/src/main/javassist/bytecode/DeprecatedAttribute.java new file mode 100644 index 0000000..41099ce --- /dev/null +++ b/src/main/javassist/bytecode/DeprecatedAttribute.java @@ -0,0 +1,55 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.bytecode; + +import java.io.DataInputStream; +import java.io.IOException; +import java.util.Map; + +/** + * <code>Deprecated_attribute</code>. + */ +public class DeprecatedAttribute extends AttributeInfo { + /** + * The name of this attribute <code>"Deprecated"</code>. + */ + public static final String tag = "Deprecated"; + + DeprecatedAttribute(ConstPool cp, int n, DataInputStream in) + throws IOException + { + super(cp, n, in); + } + + /** + * Constructs a Deprecated attribute. + * + * @param cp a constant pool table. + */ + public DeprecatedAttribute(ConstPool cp) { + super(cp, tag, new byte[0]); + } + + /** + * Makes a copy. + * + * @param newCp the constant pool table used by the new copy. + * @param classnames should be null. + */ + public AttributeInfo copy(ConstPool newCp, Map classnames) { + return new DeprecatedAttribute(newCp); + } +} diff --git a/src/main/javassist/bytecode/Descriptor.java b/src/main/javassist/bytecode/Descriptor.java new file mode 100644 index 0000000..5662222 --- /dev/null +++ b/src/main/javassist/bytecode/Descriptor.java @@ -0,0 +1,871 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.bytecode; + +import javassist.ClassPool; +import javassist.CtClass; +import javassist.CtPrimitiveType; +import javassist.NotFoundException; +import java.util.Map; + +/** + * A support class for dealing with descriptors. + * + * <p>See chapter 4.3 in "The Java Virtual Machine Specification (2nd ed.)" + */ +public class Descriptor { + /** + * Converts a class name into the internal representation used in + * the JVM. + * + * <p>Note that <code>toJvmName(toJvmName(s))</code> is equivalent + * to <code>toJvmName(s)</code>. + */ + public static String toJvmName(String classname) { + return classname.replace('.', '/'); + } + + /** + * Converts a class name from the internal representation used in + * the JVM to the normal one used in Java. + * This method does not deal with an array type name such as + * "[Ljava/lang/Object;" and "[I;". For such names, use + * <code>toClassName()</code>. + * + * @see #toClassName(String) + */ + public static String toJavaName(String classname) { + return classname.replace('/', '.'); + } + + /** + * Returns the internal representation of the class name in the + * JVM. + */ + public static String toJvmName(CtClass clazz) { + if (clazz.isArray()) + return of(clazz); + else + return toJvmName(clazz.getName()); + } + + /** + * Converts to a Java class name from a descriptor. + * + * @param descriptor type descriptor. + */ + public static String toClassName(String descriptor) { + int arrayDim = 0; + int i = 0; + char c = descriptor.charAt(0); + while (c == '[') { + ++arrayDim; + c = descriptor.charAt(++i); + } + + String name; + if (c == 'L') { + int i2 = descriptor.indexOf(';', i++); + name = descriptor.substring(i, i2).replace('/', '.'); + i = i2; + } + else if (c == 'V') + name = "void"; + else if (c == 'I') + name = "int"; + else if (c == 'B') + name = "byte"; + else if (c == 'J') + name = "long"; + else if (c == 'D') + name = "double"; + else if (c == 'F') + name = "float"; + else if (c == 'C') + name = "char"; + else if (c == 'S') + name = "short"; + else if (c == 'Z') + name = "boolean"; + else + throw new RuntimeException("bad descriptor: " + descriptor); + + if (i + 1 != descriptor.length()) + throw new RuntimeException("multiple descriptors?: " + descriptor); + + if (arrayDim == 0) + return name; + else { + StringBuffer sbuf = new StringBuffer(name); + do { + sbuf.append("[]"); + } while (--arrayDim > 0); + + return sbuf.toString(); + } + } + + /** + * Converts to a descriptor from a Java class name + */ + public static String of(String classname) { + if (classname.equals("void")) + return "V"; + else if (classname.equals("int")) + return "I"; + else if (classname.equals("byte")) + return "B"; + else if (classname.equals("long")) + return "J"; + else if (classname.equals("double")) + return "D"; + else if (classname.equals("float")) + return "F"; + else if (classname.equals("char")) + return "C"; + else if (classname.equals("short")) + return "S"; + else if (classname.equals("boolean")) + return "Z"; + else + return "L" + toJvmName(classname) + ";"; + } + + /** + * Substitutes a class name + * in the given descriptor string. + * + * @param desc descriptor string + * @param oldname replaced JVM class name + * @param newname substituted JVM class name + * + * @see Descriptor#toJvmName(String) + */ + public static String rename(String desc, String oldname, String newname) { + if (desc.indexOf(oldname) < 0) + return desc; + + StringBuffer newdesc = new StringBuffer(); + int head = 0; + int i = 0; + for (;;) { + int j = desc.indexOf('L', i); + if (j < 0) + break; + else if (desc.startsWith(oldname, j + 1) + && desc.charAt(j + oldname.length() + 1) == ';') { + newdesc.append(desc.substring(head, j)); + newdesc.append('L'); + newdesc.append(newname); + newdesc.append(';'); + head = i = j + oldname.length() + 2; + } + else { + i = desc.indexOf(';', j) + 1; + if (i < 1) + break; // ';' was not found. + } + } + + if (head == 0) + return desc; + else { + int len = desc.length(); + if (head < len) + newdesc.append(desc.substring(head, len)); + + return newdesc.toString(); + } + } + + /** + * Substitutes class names in the given descriptor string + * according to the given <code>map</code>. + * + * @param map a map between replaced and substituted + * JVM class names. + * @see Descriptor#toJvmName(String) + */ + public static String rename(String desc, Map map) { + if (map == null) + return desc; + + StringBuffer newdesc = new StringBuffer(); + int head = 0; + int i = 0; + for (;;) { + int j = desc.indexOf('L', i); + if (j < 0) + break; + + int k = desc.indexOf(';', j); + if (k < 0) + break; + + i = k + 1; + String name = desc.substring(j + 1, k); + String name2 = (String)map.get(name); + if (name2 != null) { + newdesc.append(desc.substring(head, j)); + newdesc.append('L'); + newdesc.append(name2); + newdesc.append(';'); + head = i; + } + } + + if (head == 0) + return desc; + else { + int len = desc.length(); + if (head < len) + newdesc.append(desc.substring(head, len)); + + return newdesc.toString(); + } + } + + /** + * Returns the descriptor representing the given type. + */ + public static String of(CtClass type) { + StringBuffer sbuf = new StringBuffer(); + toDescriptor(sbuf, type); + return sbuf.toString(); + } + + private static void toDescriptor(StringBuffer desc, CtClass type) { + if (type.isArray()) { + desc.append('['); + try { + toDescriptor(desc, type.getComponentType()); + } + catch (NotFoundException e) { + desc.append('L'); + String name = type.getName(); + desc.append(toJvmName(name.substring(0, name.length() - 2))); + desc.append(';'); + } + } + else if (type.isPrimitive()) { + CtPrimitiveType pt = (CtPrimitiveType)type; + desc.append(pt.getDescriptor()); + } + else { // class type + desc.append('L'); + desc.append(type.getName().replace('.', '/')); + desc.append(';'); + } + } + + /** + * Returns the descriptor representing a constructor receiving + * the given parameter types. + * + * @param paramTypes parameter types + */ + public static String ofConstructor(CtClass[] paramTypes) { + return ofMethod(CtClass.voidType, paramTypes); + } + + /** + * Returns the descriptor representing a method that receives + * the given parameter types and returns the given type. + * + * @param returnType return type + * @param paramTypes parameter types + */ + public static String ofMethod(CtClass returnType, CtClass[] paramTypes) { + StringBuffer desc = new StringBuffer(); + desc.append('('); + if (paramTypes != null) { + int n = paramTypes.length; + for (int i = 0; i < n; ++i) + toDescriptor(desc, paramTypes[i]); + } + + desc.append(')'); + if (returnType != null) + toDescriptor(desc, returnType); + + return desc.toString(); + } + + /** + * Returns the descriptor representing a list of parameter types. + * For example, if the given parameter types are two <code>int</code>, + * then this method returns <code>"(II)"</code>. + * + * @param paramTypes parameter types + */ + public static String ofParameters(CtClass[] paramTypes) { + return ofMethod(null, paramTypes); + } + + /** + * Appends a parameter type to the parameter list represented + * by the given descriptor. + * + * <p><code>classname</code> must not be an array type. + * + * @param classname parameter type (not primitive type) + * @param desc descriptor + */ + public static String appendParameter(String classname, String desc) { + int i = desc.indexOf(')'); + if (i < 0) + return desc; + else { + StringBuffer newdesc = new StringBuffer(); + newdesc.append(desc.substring(0, i)); + newdesc.append('L'); + newdesc.append(classname.replace('.', '/')); + newdesc.append(';'); + newdesc.append(desc.substring(i)); + return newdesc.toString(); + } + } + + /** + * Inserts a parameter type at the beginning of the parameter + * list represented + * by the given descriptor. + * + * <p><code>classname</code> must not be an array type. + * + * @param classname parameter type (not primitive type) + * @param desc descriptor + */ + public static String insertParameter(String classname, String desc) { + if (desc.charAt(0) != '(') + return desc; + else + return "(L" + classname.replace('.', '/') + ';' + + desc.substring(1); + } + + /** + * Appends a parameter type to the parameter list represented + * by the given descriptor. The appended parameter becomes + * the last parameter. + * + * @param type the type of the appended parameter. + * @param descriptor the original descriptor. + */ + public static String appendParameter(CtClass type, String descriptor) { + int i = descriptor.indexOf(')'); + if (i < 0) + return descriptor; + else { + StringBuffer newdesc = new StringBuffer(); + newdesc.append(descriptor.substring(0, i)); + toDescriptor(newdesc, type); + newdesc.append(descriptor.substring(i)); + return newdesc.toString(); + } + } + + /** + * Inserts a parameter type at the beginning of the parameter + * list represented + * by the given descriptor. + * + * @param type the type of the inserted parameter. + * @param descriptor the descriptor of the method. + */ + public static String insertParameter(CtClass type, + String descriptor) { + if (descriptor.charAt(0) != '(') + return descriptor; + else + return "(" + of(type) + descriptor.substring(1); + } + + /** + * Changes the return type included in the given descriptor. + * + * <p><code>classname</code> must not be an array type. + * + * @param classname return type + * @param desc descriptor + */ + public static String changeReturnType(String classname, String desc) { + int i = desc.indexOf(')'); + if (i < 0) + return desc; + else { + StringBuffer newdesc = new StringBuffer(); + newdesc.append(desc.substring(0, i + 1)); + newdesc.append('L'); + newdesc.append(classname.replace('.', '/')); + newdesc.append(';'); + return newdesc.toString(); + } + } + + /** + * Returns the <code>CtClass</code> objects representing the parameter + * types specified by the given descriptor. + * + * @param desc descriptor + * @param cp the class pool used for obtaining + * a <code>CtClass</code> object. + */ + public static CtClass[] getParameterTypes(String desc, ClassPool cp) + throws NotFoundException + { + if (desc.charAt(0) != '(') + return null; + else { + int num = numOfParameters(desc); + CtClass[] args = new CtClass[num]; + int n = 0; + int i = 1; + do { + i = toCtClass(cp, desc, i, args, n++); + } while (i > 0); + return args; + } + } + + /** + * Returns true if the list of the parameter types of desc1 is equal to + * that of desc2. + * For example, "(II)V" and "(II)I" are equal. + */ + public static boolean eqParamTypes(String desc1, String desc2) { + if (desc1.charAt(0) != '(') + return false; + + for (int i = 0; true; ++i) { + char c = desc1.charAt(i); + if (c != desc2.charAt(i)) + return false; + + if (c == ')') + return true; + } + } + + /** + * Returns the signature of the given descriptor. The signature does + * not include the return type. For example, the signature of "(I)V" + * is "(I)". + */ + public static String getParamDescriptor(String decl) { + return decl.substring(0, decl.indexOf(')') + 1); + } + + /** + * Returns the <code>CtClass</code> object representing the return + * type specified by the given descriptor. + * + * @param desc descriptor + * @param cp the class pool used for obtaining + * a <code>CtClass</code> object. + */ + public static CtClass getReturnType(String desc, ClassPool cp) + throws NotFoundException + { + int i = desc.indexOf(')'); + if (i < 0) + return null; + else { + CtClass[] type = new CtClass[1]; + toCtClass(cp, desc, i + 1, type, 0); + return type[0]; + } + } + + /** + * Returns the number of the prameters included in the given + * descriptor. + * + * @param desc descriptor + */ + public static int numOfParameters(String desc) { + int n = 0; + int i = 1; + for (;;) { + char c = desc.charAt(i); + if (c == ')') + break; + + while (c == '[') + c = desc.charAt(++i); + + if (c == 'L') { + i = desc.indexOf(';', i) + 1; + if (i <= 0) + throw new IndexOutOfBoundsException("bad descriptor"); + } + else + ++i; + + ++n; + } + + return n; + } + + /** + * Returns a <code>CtClass</code> object representing the type + * specified by the given descriptor. + * + * <p>This method works even if the package-class separator is + * not <code>/</code> but <code>.</code> (period). For example, + * it accepts <code>Ljava.lang.Object;</code> + * as well as <code>Ljava/lang/Object;</code>. + * + * @param desc descriptor. + * @param cp the class pool used for obtaining + * a <code>CtClass</code> object. + */ + public static CtClass toCtClass(String desc, ClassPool cp) + throws NotFoundException + { + CtClass[] clazz = new CtClass[1]; + int res = toCtClass(cp, desc, 0, clazz, 0); + if (res >= 0) + return clazz[0]; + else { + // maybe, you forgot to surround the class name with + // L and ;. It violates the protocol, but I'm tolerant... + return cp.get(desc.replace('/', '.')); + } + } + + private static int toCtClass(ClassPool cp, String desc, int i, + CtClass[] args, int n) + throws NotFoundException + { + int i2; + String name; + + int arrayDim = 0; + char c = desc.charAt(i); + while (c == '[') { + ++arrayDim; + c = desc.charAt(++i); + } + + if (c == 'L') { + i2 = desc.indexOf(';', ++i); + name = desc.substring(i, i2++).replace('/', '.'); + } + else { + CtClass type = toPrimitiveClass(c); + if (type == null) + return -1; // error + + i2 = i + 1; + if (arrayDim == 0) { + args[n] = type; + return i2; // neither an array type or a class type + } + else + name = type.getName(); + } + + if (arrayDim > 0) { + StringBuffer sbuf = new StringBuffer(name); + while (arrayDim-- > 0) + sbuf.append("[]"); + + name = sbuf.toString(); + } + + args[n] = cp.get(name); + return i2; + } + + static CtClass toPrimitiveClass(char c) { + CtClass type = null; + switch (c) { + case 'Z' : + type = CtClass.booleanType; + break; + case 'C' : + type = CtClass.charType; + break; + case 'B' : + type = CtClass.byteType; + break; + case 'S' : + type = CtClass.shortType; + break; + case 'I' : + type = CtClass.intType; + break; + case 'J' : + type = CtClass.longType; + break; + case 'F' : + type = CtClass.floatType; + break; + case 'D' : + type = CtClass.doubleType; + break; + case 'V' : + type = CtClass.voidType; + break; + } + + return type; + } + + /** + * Computes the dimension of the array represented by the given + * descriptor. For example, if the descriptor is <code>"[[I"</code>, + * then this method returns 2. + * + * @param desc the descriptor. + * @return 0 if the descriptor does not represent an array type. + */ + public static int arrayDimension(String desc) { + int dim = 0; + while (desc.charAt(dim) == '[') + ++dim; + + return dim; + } + + /** + * Returns the descriptor of the type of the array component. + * For example, if the given descriptor is + * <code>"[[Ljava/lang/String;"</code> and the given dimension is 2, + * then this method returns <code>"Ljava/lang/String;"</code>. + * + * @param desc the descriptor. + * @param dim the array dimension. + */ + public static String toArrayComponent(String desc, int dim) { + return desc.substring(dim); + } + + /** + * Computes the data size specified by the given descriptor. + * For example, if the descriptor is "D", this method returns 2. + * + * <p>If the descriptor represents a method type, this method returns + * (the size of the returned value) - (the sum of the data sizes + * of all the parameters). For example, if the descriptor is + * <code>"(I)D"</code>, then this method returns 1 (= 2 - 1). + * + * @param desc descriptor + */ + public static int dataSize(String desc) { + return dataSize(desc, true); + } + + /** + * Computes the data size of parameters. + * If one of the parameters is double type, the size of that parameter + * is 2 words. For example, if the given descriptor is + * <code>"(IJ)D"</code>, then this method returns 3. The size of the + * return type is not computed. + * + * @param desc a method descriptor. + */ + public static int paramSize(String desc) { + return -dataSize(desc, false); + } + + private static int dataSize(String desc, boolean withRet) { + int n = 0; + char c = desc.charAt(0); + if (c == '(') { + int i = 1; + for (;;) { + c = desc.charAt(i); + if (c == ')') { + c = desc.charAt(i + 1); + break; + } + + boolean array = false; + while (c == '[') { + array = true; + c = desc.charAt(++i); + } + + if (c == 'L') { + i = desc.indexOf(';', i) + 1; + if (i <= 0) + throw new IndexOutOfBoundsException("bad descriptor"); + } + else + ++i; + + if (!array && (c == 'J' || c == 'D')) + n -= 2; + else + --n; + } + } + + if (withRet) + if (c == 'J' || c == 'D') + n += 2; + else if (c != 'V') + ++n; + + return n; + } + + /** + * Returns a human-readable representation of the + * given descriptor. For example, <code>Ljava/lang/Object;</code> + * is converted into <code>java.lang.Object</code>. + * <code>(I[I)V</code> is converted into <code>(int, int[])</code> + * (the return type is ignored). + */ + public static String toString(String desc) { + return PrettyPrinter.toString(desc); + } + + static class PrettyPrinter { + static String toString(String desc) { + StringBuffer sbuf = new StringBuffer(); + if (desc.charAt(0) == '(') { + int pos = 1; + sbuf.append('('); + while (desc.charAt(pos) != ')') { + if (pos > 1) + sbuf.append(','); + + pos = readType(sbuf, pos, desc); + } + + sbuf.append(')'); + } + else + readType(sbuf, 0, desc); + + return sbuf.toString(); + } + + static int readType(StringBuffer sbuf, int pos, String desc) { + char c = desc.charAt(pos); + int arrayDim = 0; + while (c == '[') { + arrayDim++; + c = desc.charAt(++pos); + } + + if (c == 'L') + while (true) { + c = desc.charAt(++pos); + if (c == ';') + break; + + if (c == '/') + c = '.'; + + sbuf.append(c); + } + else { + CtClass t = toPrimitiveClass(c); + sbuf.append(t.getName()); + } + + while (arrayDim-- > 0) + sbuf.append("[]"); + + return pos + 1; + } + } + + /** + * An Iterator over a descriptor. + */ + public static class Iterator { + private String desc; + private int index, curPos; + private boolean param; + + /** + * Constructs an iterator. + * + * @param s descriptor. + */ + public Iterator(String s) { + desc = s; + index = curPos = 0; + param = false; + } + + /** + * Returns true if the iteration has more elements. + */ + public boolean hasNext() { + return index < desc.length(); + } + + /** + * Returns true if the current element is a parameter type. + */ + public boolean isParameter() { return param; } + + /** + * Returns the first character of the current element. + */ + public char currentChar() { return desc.charAt(curPos); } + + /** + * Returns true if the current element is double or long type. + */ + public boolean is2byte() { + char c = currentChar(); + return c == 'D' || c == 'J'; + } + + /** + * Returns the position of the next type character. + * That type character becomes a new current element. + */ + public int next() { + int nextPos = index; + char c = desc.charAt(nextPos); + if (c == '(') { + ++index; + c = desc.charAt(++nextPos); + param = true; + } + + if (c == ')') { + ++index; + c = desc.charAt(++nextPos); + param = false; + } + + while (c == '[') + c = desc.charAt(++nextPos); + + if (c == 'L') { + nextPos = desc.indexOf(';', nextPos) + 1; + if (nextPos <= 0) + throw new IndexOutOfBoundsException("bad descriptor"); + } + else + ++nextPos; + + curPos = index; + index = nextPos; + return curPos; + } + } +} diff --git a/src/main/javassist/bytecode/DuplicateMemberException.java b/src/main/javassist/bytecode/DuplicateMemberException.java new file mode 100644 index 0000000..7c1e0e6 --- /dev/null +++ b/src/main/javassist/bytecode/DuplicateMemberException.java @@ -0,0 +1,30 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.bytecode; + +import javassist.CannotCompileException; + +/** + * An exception thrown when adding a duplicate member is requested. + * + * @see ClassFile#addMethod(MethodInfo) + * @see ClassFile#addField(FieldInfo) + */ +public class DuplicateMemberException extends CannotCompileException { + public DuplicateMemberException(String msg) { + super(msg); + } +} diff --git a/src/main/javassist/bytecode/EnclosingMethodAttribute.java b/src/main/javassist/bytecode/EnclosingMethodAttribute.java new file mode 100644 index 0000000..c924f50 --- /dev/null +++ b/src/main/javassist/bytecode/EnclosingMethodAttribute.java @@ -0,0 +1,133 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.bytecode; + +import java.io.DataInputStream; +import java.io.IOException; +import java.util.Map; + +/** + * <code>EnclosingMethod_attribute</code>. + */ +public class EnclosingMethodAttribute extends AttributeInfo { + /** + * The name of this attribute <code>"EnclosingMethod"</code>. + */ + public static final String tag = "EnclosingMethod"; + + EnclosingMethodAttribute(ConstPool cp, int n, DataInputStream in) + throws IOException + { + super(cp, n, in); + } + + /** + * Constructs an EnclosingMethod attribute. + * + * @param cp a constant pool table. + * @param className the name of the innermost enclosing class. + * @param methodName the name of the enclosing method. + * @param methodDesc the descriptor of the enclosing method. + */ + public EnclosingMethodAttribute(ConstPool cp, String className, + String methodName, String methodDesc) { + super(cp, tag); + int ci = cp.addClassInfo(className); + int ni = cp.addNameAndTypeInfo(methodName, methodDesc); + byte[] bvalue = new byte[4]; + bvalue[0] = (byte)(ci >>> 8); + bvalue[1] = (byte)ci; + bvalue[2] = (byte)(ni >>> 8); + bvalue[3] = (byte)ni; + set(bvalue); + } + + /** + * Constructs an EnclosingMethod attribute. + * The value of <code>method_index</code> is set to 0. + * + * @param cp a constant pool table. + * @param className the name of the innermost enclosing class. + */ + public EnclosingMethodAttribute(ConstPool cp, String className) { + super(cp, tag); + int ci = cp.addClassInfo(className); + int ni = 0; + byte[] bvalue = new byte[4]; + bvalue[0] = (byte)(ci >>> 8); + bvalue[1] = (byte)ci; + bvalue[2] = (byte)(ni >>> 8); + bvalue[3] = (byte)ni; + set(bvalue); + } + + /** + * Returns the value of <code>class_index</code>. + */ + public int classIndex() { + return ByteArray.readU16bit(get(), 0); + } + + /** + * Returns the value of <code>method_index</code>. + */ + public int methodIndex() { + return ByteArray.readU16bit(get(), 2); + } + + /** + * Returns the name of the class specified by <code>class_index</code>. + */ + public String className() { + return getConstPool().getClassInfo(classIndex()); + } + + /** + * Returns the method name specified by <code>method_index</code>. + */ + public String methodName() { + ConstPool cp = getConstPool(); + int mi = methodIndex(); + int ni = cp.getNameAndTypeName(mi); + return cp.getUtf8Info(ni); + } + + /** + * Returns the method descriptor specified by <code>method_index</code>. + */ + public String methodDescriptor() { + ConstPool cp = getConstPool(); + int mi = methodIndex(); + int ti = cp.getNameAndTypeDescriptor(mi); + return cp.getUtf8Info(ti); + } + + /** + * Makes a copy. Class names are replaced according to the + * given <code>Map</code> object. + * + * @param newCp the constant pool table used by the new copy. + * @param classnames pairs of replaced and substituted + * class names. + */ + public AttributeInfo copy(ConstPool newCp, Map classnames) { + if (methodIndex() == 0) + return new EnclosingMethodAttribute(newCp, className()); + else + return new EnclosingMethodAttribute(newCp, className(), + methodName(), methodDescriptor()); + } +} diff --git a/src/main/javassist/bytecode/ExceptionTable.java b/src/main/javassist/bytecode/ExceptionTable.java new file mode 100644 index 0000000..1c74e6e --- /dev/null +++ b/src/main/javassist/bytecode/ExceptionTable.java @@ -0,0 +1,280 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.bytecode; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Map; + +class ExceptionTableEntry { + int startPc; + int endPc; + int handlerPc; + int catchType; + + ExceptionTableEntry(int start, int end, int handle, int type) { + startPc = start; + endPc = end; + handlerPc = handle; + catchType = type; + } +} + +/** + * <code>exception_table[]</code> of <code>Code_attribute</code>. + */ +public class ExceptionTable implements Cloneable { + private ConstPool constPool; + private ArrayList entries; + + /** + * Constructs an <code>exception_table[]</code>. + * + * @param cp constant pool table. + */ + public ExceptionTable(ConstPool cp) { + constPool = cp; + entries = new ArrayList(); + } + + ExceptionTable(ConstPool cp, DataInputStream in) throws IOException { + constPool = cp; + int length = in.readUnsignedShort(); + ArrayList list = new ArrayList(length); + for (int i = 0; i < length; ++i) { + int start = in.readUnsignedShort(); + int end = in.readUnsignedShort(); + int handle = in.readUnsignedShort(); + int type = in.readUnsignedShort(); + list.add(new ExceptionTableEntry(start, end, handle, type)); + } + + entries = list; + } + + /** + * Creates and returns a copy of this object. + * The constant pool object is shared between this object + * and the cloned object. + */ + public Object clone() throws CloneNotSupportedException { + ExceptionTable r = (ExceptionTable)super.clone(); + r.entries = new ArrayList(entries); + return r; + } + + /** + * Returns <code>exception_table_length</code>, which is the number + * of entries in the <code>exception_table[]</code>. + */ + public int size() { + return entries.size(); + } + + /** + * Returns <code>startPc</code> of the <i>n</i>-th entry. + * + * @param nth the <i>n</i>-th (>= 0). + */ + public int startPc(int nth) { + ExceptionTableEntry e = (ExceptionTableEntry)entries.get(nth); + return e.startPc; + } + + /** + * Sets <code>startPc</code> of the <i>n</i>-th entry. + * + * @param nth the <i>n</i>-th (>= 0). + * @param value new value. + */ + public void setStartPc(int nth, int value) { + ExceptionTableEntry e = (ExceptionTableEntry)entries.get(nth); + e.startPc = value; + } + + /** + * Returns <code>endPc</code> of the <i>n</i>-th entry. + * + * @param nth the <i>n</i>-th (>= 0). + */ + public int endPc(int nth) { + ExceptionTableEntry e = (ExceptionTableEntry)entries.get(nth); + return e.endPc; + } + + /** + * Sets <code>endPc</code> of the <i>n</i>-th entry. + * + * @param nth the <i>n</i>-th (>= 0). + * @param value new value. + */ + public void setEndPc(int nth, int value) { + ExceptionTableEntry e = (ExceptionTableEntry)entries.get(nth); + e.endPc = value; + } + + /** + * Returns <code>handlerPc</code> of the <i>n</i>-th entry. + * + * @param nth the <i>n</i>-th (>= 0). + */ + public int handlerPc(int nth) { + ExceptionTableEntry e = (ExceptionTableEntry)entries.get(nth); + return e.handlerPc; + } + + /** + * Sets <code>handlerPc</code> of the <i>n</i>-th entry. + * + * @param nth the <i>n</i>-th (>= 0). + * @param value new value. + */ + public void setHandlerPc(int nth, int value) { + ExceptionTableEntry e = (ExceptionTableEntry)entries.get(nth); + e.handlerPc = value; + } + + /** + * Returns <code>catchType</code> of the <i>n</i>-th entry. + * + * @param nth the <i>n</i>-th (>= 0). + * @return an index into the <code>constant_pool</code> table, + * or zero if this exception handler is for all exceptions. + */ + public int catchType(int nth) { + ExceptionTableEntry e = (ExceptionTableEntry)entries.get(nth); + return e.catchType; + } + + /** + * Sets <code>catchType</code> of the <i>n</i>-th entry. + * + * @param nth the <i>n</i>-th (>= 0). + * @param value new value. + */ + public void setCatchType(int nth, int value) { + ExceptionTableEntry e = (ExceptionTableEntry)entries.get(nth); + e.catchType = value; + } + + /** + * Copies the given exception table at the specified position + * in the table. + * + * @param index index (>= 0) at which the entry is to be inserted. + * @param offset the offset added to the code position. + */ + public void add(int index, ExceptionTable table, int offset) { + int len = table.size(); + while (--len >= 0) { + ExceptionTableEntry e + = (ExceptionTableEntry)table.entries.get(len); + add(index, e.startPc + offset, e.endPc + offset, + e.handlerPc + offset, e.catchType); + } + } + + /** + * Adds a new entry at the specified position in the table. + * + * @param index index (>= 0) at which the entry is to be inserted. + * @param start <code>startPc</code> + * @param end <code>endPc</code> + * @param handler <code>handlerPc</code> + * @param type <code>catchType</code> + */ + public void add(int index, int start, int end, int handler, int type) { + if (start < end) + entries.add(index, + new ExceptionTableEntry(start, end, handler, type)); + } + + /** + * Appends a new entry at the end of the table. + * + * @param start <code>startPc</code> + * @param end <code>endPc</code> + * @param handler <code>handlerPc</code> + * @param type <code>catchType</code> + */ + public void add(int start, int end, int handler, int type) { + if (start < end) + entries.add(new ExceptionTableEntry(start, end, handler, type)); + } + + /** + * Removes the entry at the specified position in the table. + * + * @param index the index of the removed entry. + */ + public void remove(int index) { + entries.remove(index); + } + + /** + * Makes a copy of this <code>exception_table[]</code>. + * Class names are replaced according to the + * given <code>Map</code> object. + * + * @param newCp the constant pool table used by the new copy. + * @param classnames pairs of replaced and substituted + * class names. + */ + public ExceptionTable copy(ConstPool newCp, Map classnames) { + ExceptionTable et = new ExceptionTable(newCp); + ConstPool srcCp = constPool; + int len = size(); + for (int i = 0; i < len; ++i) { + ExceptionTableEntry e = (ExceptionTableEntry)entries.get(i); + int type = srcCp.copy(e.catchType, newCp, classnames); + et.add(e.startPc, e.endPc, e.handlerPc, type); + } + + return et; + } + + void shiftPc(int where, int gapLength, boolean exclusive) { + int len = size(); + for (int i = 0; i < len; ++i) { + ExceptionTableEntry e = (ExceptionTableEntry)entries.get(i); + e.startPc = shiftPc(e.startPc, where, gapLength, exclusive); + e.endPc = shiftPc(e.endPc, where, gapLength, exclusive); + e.handlerPc = shiftPc(e.handlerPc, where, gapLength, exclusive); + } + } + + private static int shiftPc(int pc, int where, int gapLength, + boolean exclusive) { + if (pc > where || (exclusive && pc == where)) + pc += gapLength; + + return pc; + } + + void write(DataOutputStream out) throws IOException { + int len = size(); + out.writeShort(len); // exception_table_length + for (int i = 0; i < len; ++i) { + ExceptionTableEntry e = (ExceptionTableEntry)entries.get(i); + out.writeShort(e.startPc); + out.writeShort(e.endPc); + out.writeShort(e.handlerPc); + out.writeShort(e.catchType); + } + } +} diff --git a/src/main/javassist/bytecode/ExceptionsAttribute.java b/src/main/javassist/bytecode/ExceptionsAttribute.java new file mode 100644 index 0000000..2fe34dd --- /dev/null +++ b/src/main/javassist/bytecode/ExceptionsAttribute.java @@ -0,0 +1,173 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.bytecode; + +import java.io.DataInputStream; +import java.io.IOException; +import java.util.Map; + +/** + * <code>Exceptions_attribute</code>. + */ +public class ExceptionsAttribute extends AttributeInfo { + /** + * The name of this attribute <code>"Exceptions"</code>. + */ + public static final String tag = "Exceptions"; + + ExceptionsAttribute(ConstPool cp, int n, DataInputStream in) + throws IOException + { + super(cp, n, in); + } + + /** + * Constructs a copy of an exceptions attribute. + * + * @param cp constant pool table. + * @param src source attribute. + */ + private ExceptionsAttribute(ConstPool cp, ExceptionsAttribute src, + Map classnames) { + super(cp, tag); + copyFrom(src, classnames); + } + + /** + * Constructs a new exceptions attribute. + * + * @param cp constant pool table. + */ + public ExceptionsAttribute(ConstPool cp) { + super(cp, tag); + byte[] data = new byte[2]; + data[0] = data[1] = 0; // empty + this.info = data; + } + + /** + * Makes a copy. Class names are replaced according to the + * given <code>Map</code> object. + * + * @param newCp the constant pool table used by the new copy. + * @param classnames pairs of replaced and substituted + * class names. It can be <code>null</code>. + */ + public AttributeInfo copy(ConstPool newCp, Map classnames) { + return new ExceptionsAttribute(newCp, this, classnames); + } + + /** + * Copies the contents from a source attribute. + * Specified class names are replaced during the copy. + * + * @param srcAttr source Exceptions attribute + * @param classnames pairs of replaced and substituted + * class names. + */ + private void copyFrom(ExceptionsAttribute srcAttr, Map classnames) { + ConstPool srcCp = srcAttr.constPool; + ConstPool destCp = this.constPool; + byte[] src = srcAttr.info; + int num = src.length; + byte[] dest = new byte[num]; + dest[0] = src[0]; + dest[1] = src[1]; // the number of elements. + for (int i = 2; i < num; i += 2) { + int index = ByteArray.readU16bit(src, i); + ByteArray.write16bit(srcCp.copy(index, destCp, classnames), + dest, i); + } + + this.info = dest; + } + + /** + * Returns <code>exception_index_table[]</code>. + */ + public int[] getExceptionIndexes() { + byte[] blist = info; + int n = blist.length; + if (n <= 2) + return null; + + int[] elist = new int[n / 2 - 1]; + int k = 0; + for (int j = 2; j < n; j += 2) + elist[k++] = ((blist[j] & 0xff) << 8) | (blist[j + 1] & 0xff); + + return elist; + } + + /** + * Returns the names of exceptions that the method may throw. + */ + public String[] getExceptions() { + byte[] blist = info; + int n = blist.length; + if (n <= 2) + return null; + + String[] elist = new String[n / 2 - 1]; + int k = 0; + for (int j = 2; j < n; j += 2) { + int index = ((blist[j] & 0xff) << 8) | (blist[j + 1] & 0xff); + elist[k++] = constPool.getClassInfo(index); + } + + return elist; + } + + /** + * Sets <code>exception_index_table[]</code>. + */ + public void setExceptionIndexes(int[] elist) { + int n = elist.length; + byte[] blist = new byte[n * 2 + 2]; + ByteArray.write16bit(n, blist, 0); + for (int i = 0; i < n; ++i) + ByteArray.write16bit(elist[i], blist, i * 2 + 2); + + info = blist; + } + + /** + * Sets the names of exceptions that the method may throw. + */ + public void setExceptions(String[] elist) { + int n = elist.length; + byte[] blist = new byte[n * 2 + 2]; + ByteArray.write16bit(n, blist, 0); + for (int i = 0; i < n; ++i) + ByteArray.write16bit(constPool.addClassInfo(elist[i]), + blist, i * 2 + 2); + + info = blist; + } + + /** + * Returns <code>number_of_exceptions</code>. + */ + public int tableLength() { return info.length / 2 - 1; } + + /** + * Returns the value of <code>exception_index_table[nth]</code>. + */ + public int getException(int nth) { + int index = nth * 2 + 2; // nth >= 0 + return ((info[index] & 0xff) << 8) | (info[index + 1] & 0xff); + } +} diff --git a/src/main/javassist/bytecode/FieldInfo.java b/src/main/javassist/bytecode/FieldInfo.java new file mode 100644 index 0000000..f474373 --- /dev/null +++ b/src/main/javassist/bytecode/FieldInfo.java @@ -0,0 +1,266 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.bytecode; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.List; +import java.util.ArrayList; + +/** + * <code>field_info</code> structure. + * + * @see javassist.CtField#getFieldInfo() + */ +public final class FieldInfo { + ConstPool constPool; + int accessFlags; + int name; + String cachedName; + String cachedType; + int descriptor; + ArrayList attribute; // may be null. + + private FieldInfo(ConstPool cp) { + constPool = cp; + accessFlags = 0; + attribute = null; + } + + /** + * Constructs a <code>field_info</code> structure. + * + * @param cp a constant pool table + * @param fieldName field name + * @param desc field descriptor + * + * @see Descriptor + */ + public FieldInfo(ConstPool cp, String fieldName, String desc) { + this(cp); + name = cp.addUtf8Info(fieldName); + cachedName = fieldName; + descriptor = cp.addUtf8Info(desc); + } + + FieldInfo(ConstPool cp, DataInputStream in) throws IOException { + this(cp); + read(in); + } + + /** + * Returns a string representation of the object. + */ + public String toString() { + return getName() + " " + getDescriptor(); + } + + /** + * Copies all constant pool items to a given new constant pool + * and replaces the original items with the new ones. + * This is used for garbage collecting the items of removed fields + * and methods. + * + * @param cp the destination + */ + void compact(ConstPool cp) { + name = cp.addUtf8Info(getName()); + descriptor = cp.addUtf8Info(getDescriptor()); + attribute = AttributeInfo.copyAll(attribute, cp); + constPool = cp; + } + + void prune(ConstPool cp) { + ArrayList newAttributes = new ArrayList(); + AttributeInfo invisibleAnnotations + = getAttribute(AnnotationsAttribute.invisibleTag); + if (invisibleAnnotations != null) { + invisibleAnnotations = invisibleAnnotations.copy(cp, null); + newAttributes.add(invisibleAnnotations); + } + + AttributeInfo visibleAnnotations + = getAttribute(AnnotationsAttribute.visibleTag); + if (visibleAnnotations != null) { + visibleAnnotations = visibleAnnotations.copy(cp, null); + newAttributes.add(visibleAnnotations); + } + + AttributeInfo signature + = getAttribute(SignatureAttribute.tag); + if (signature != null) { + signature = signature.copy(cp, null); + newAttributes.add(signature); + } + + int index = getConstantValue(); + if (index != 0) { + index = constPool.copy(index, cp, null); + newAttributes.add(new ConstantAttribute(cp, index)); + } + + attribute = newAttributes; + name = cp.addUtf8Info(getName()); + descriptor = cp.addUtf8Info(getDescriptor()); + constPool = cp; + } + + /** + * Returns the constant pool table used + * by this <code>field_info</code>. + */ + public ConstPool getConstPool() { + return constPool; + } + + /** + * Returns the field name. + */ + public String getName() { + if (cachedName == null) + cachedName = constPool.getUtf8Info(name); + + return cachedName; + } + + /** + * Sets the field name. + */ + public void setName(String newName) { + name = constPool.addUtf8Info(newName); + cachedName = newName; + } + + /** + * Returns the access flags. + * + * @see AccessFlag + */ + public int getAccessFlags() { + return accessFlags; + } + + /** + * Sets the access flags. + * + * @see AccessFlag + */ + public void setAccessFlags(int acc) { + accessFlags = acc; + } + + /** + * Returns the field descriptor. + * + * @see Descriptor + */ + public String getDescriptor() { + return constPool.getUtf8Info(descriptor); + } + + /** + * Sets the field descriptor. + * + * @see Descriptor + */ + public void setDescriptor(String desc) { + if (!desc.equals(getDescriptor())) + descriptor = constPool.addUtf8Info(desc); + } + + /** + * Finds a ConstantValue attribute and returns the index into + * the <code>constant_pool</code> table. + * + * @return 0 if a ConstantValue attribute is not found. + */ + public int getConstantValue() { + if ((accessFlags & AccessFlag.STATIC) == 0) + return 0; + + ConstantAttribute attr + = (ConstantAttribute)getAttribute(ConstantAttribute.tag); + if (attr == null) + return 0; + else + return attr.getConstantValue(); + } + + /** + * Returns all the attributes. The returned <code>List</code> object + * is shared with this object. If you add a new attribute to the list, + * the attribute is also added to the field represented by this + * object. If you remove an attribute from the list, it is also removed + * from the field. + * + * @return a list of <code>AttributeInfo</code> objects. + * @see AttributeInfo + */ + public List getAttributes() { + if (attribute == null) + attribute = new ArrayList(); + + return attribute; + } + + /** + * Returns the attribute with the specified name. + * It returns null if the specified attribute is not found. + * + * @param name attribute name + * @see #getAttributes() + */ + public AttributeInfo getAttribute(String name) { + return AttributeInfo.lookup(attribute, name); + } + + /** + * Appends an attribute. If there is already an attribute with + * the same name, the new one substitutes for it. + * + * @see #getAttributes() + */ + public void addAttribute(AttributeInfo info) { + if (attribute == null) + attribute = new ArrayList(); + + AttributeInfo.remove(attribute, info.getName()); + attribute.add(info); + } + + private void read(DataInputStream in) throws IOException { + accessFlags = in.readUnsignedShort(); + name = in.readUnsignedShort(); + descriptor = in.readUnsignedShort(); + int n = in.readUnsignedShort(); + attribute = new ArrayList(); + for (int i = 0; i < n; ++i) + attribute.add(AttributeInfo.read(constPool, in)); + } + + void write(DataOutputStream out) throws IOException { + out.writeShort(accessFlags); + out.writeShort(name); + out.writeShort(descriptor); + if (attribute == null) + out.writeShort(0); + else { + out.writeShort(attribute.size()); + AttributeInfo.writeAll(attribute, out); + } + } +} diff --git a/src/main/javassist/bytecode/InnerClassesAttribute.java b/src/main/javassist/bytecode/InnerClassesAttribute.java new file mode 100644 index 0000000..df5645a --- /dev/null +++ b/src/main/javassist/bytecode/InnerClassesAttribute.java @@ -0,0 +1,241 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.bytecode; + +import java.io.DataInputStream; +import java.util.Map; +import java.io.IOException; + +/** + * <code>InnerClasses_attribute</code>. + */ +public class InnerClassesAttribute extends AttributeInfo { + /** + * The name of this attribute <code>"InnerClasses"</code>. + */ + public static final String tag = "InnerClasses"; + + InnerClassesAttribute(ConstPool cp, int n, DataInputStream in) + throws IOException + { + super(cp, n, in); + } + + private InnerClassesAttribute(ConstPool cp, byte[] info) { + super(cp, tag, info); + } + + /** + * Constructs an empty InnerClasses attribute. + * + * @see #append(String, String, String, int) + */ + public InnerClassesAttribute(ConstPool cp) { + super(cp, tag, new byte[2]); + ByteArray.write16bit(0, get(), 0); + } + + /** + * Returns <code>number_of_classes</code>. + */ + public int tableLength() { return ByteArray.readU16bit(get(), 0); } + + /** + * Returns <code>classes[nth].inner_class_info_index</code>. + */ + public int innerClassIndex(int nth) { + return ByteArray.readU16bit(get(), nth * 8 + 2); + } + + /** + * Returns the class name indicated + * by <code>classes[nth].inner_class_info_index</code>. + * + * @return null or the class name. + */ + public String innerClass(int nth) { + int i = innerClassIndex(nth); + if (i == 0) + return null; + else + return constPool.getClassInfo(i); + } + + /** + * Sets <code>classes[nth].inner_class_info_index</code> to + * the given index. + */ + public void setInnerClassIndex(int nth, int index) { + ByteArray.write16bit(index, get(), nth * 8 + 2); + } + + /** + * Returns <code>classes[nth].outer_class_info_index</code>. + */ + public int outerClassIndex(int nth) { + return ByteArray.readU16bit(get(), nth * 8 + 4); + } + + /** + * Returns the class name indicated + * by <code>classes[nth].outer_class_info_index</code>. + * + * @return null or the class name. + */ + public String outerClass(int nth) { + int i = outerClassIndex(nth); + if (i == 0) + return null; + else + return constPool.getClassInfo(i); + } + + /** + * Sets <code>classes[nth].outer_class_info_index</code> to + * the given index. + */ + public void setOuterClassIndex(int nth, int index) { + ByteArray.write16bit(index, get(), nth * 8 + 4); + } + + /** + * Returns <code>classes[nth].inner_name_index</code>. + */ + public int innerNameIndex(int nth) { + return ByteArray.readU16bit(get(), nth * 8 + 6); + } + + /** + * Returns the simple class name indicated + * by <code>classes[nth].inner_name_index</code>. + * + * @return null or the class name. + */ + public String innerName(int nth) { + int i = innerNameIndex(nth); + if (i == 0) + return null; + else + return constPool.getUtf8Info(i); + } + + /** + * Sets <code>classes[nth].inner_name_index</code> to + * the given index. + */ + public void setInnerNameIndex(int nth, int index) { + ByteArray.write16bit(index, get(), nth * 8 + 6); + } + + /** + * Returns <code>classes[nth].inner_class_access_flags</code>. + */ + public int accessFlags(int nth) { + return ByteArray.readU16bit(get(), nth * 8 + 8); + } + + /** + * Sets <code>classes[nth].inner_class_access_flags</code> to + * the given index. + */ + public void setAccessFlags(int nth, int flags) { + ByteArray.write16bit(flags, get(), nth * 8 + 8); + } + + /** + * Appends a new entry. + * + * @param inner <code>inner_class_info_index</code> + * @param outer <code>outer_class_info_index</code> + * @param name <code>inner_name_index</code> + * @param flags <code>inner_class_access_flags</code> + */ + public void append(String inner, String outer, String name, int flags) { + int i = constPool.addClassInfo(inner); + int o = constPool.addClassInfo(outer); + int n = constPool.addUtf8Info(name); + append(i, o, n, flags); + } + + /** + * Appends a new entry. + * + * @param inner <code>inner_class_info_index</code> + * @param outer <code>outer_class_info_index</code> + * @param name <code>inner_name_index</code> + * @param flags <code>inner_class_access_flags</code> + */ + public void append(int inner, int outer, int name, int flags) { + byte[] data = get(); + int len = data.length; + byte[] newData = new byte[len + 8]; + for (int i = 2; i < len; ++i) + newData[i] = data[i]; + + int n = ByteArray.readU16bit(data, 0); + ByteArray.write16bit(n + 1, newData, 0); + + ByteArray.write16bit(inner, newData, len); + ByteArray.write16bit(outer, newData, len + 2); + ByteArray.write16bit(name, newData, len + 4); + ByteArray.write16bit(flags, newData, len + 6); + + set(newData); + } + + /** + * Makes a copy. Class names are replaced according to the + * given <code>Map</code> object. + * + * @param newCp the constant pool table used by the new copy. + * @param classnames pairs of replaced and substituted + * class names. + */ + public AttributeInfo copy(ConstPool newCp, Map classnames) { + byte[] src = get(); + byte[] dest = new byte[src.length]; + ConstPool cp = getConstPool(); + InnerClassesAttribute attr = new InnerClassesAttribute(newCp, dest); + int n = ByteArray.readU16bit(src, 0); + ByteArray.write16bit(n, dest, 0); + int j = 2; + for (int i = 0; i < n; ++i) { + int innerClass = ByteArray.readU16bit(src, j); + int outerClass = ByteArray.readU16bit(src, j + 2); + int innerName = ByteArray.readU16bit(src, j + 4); + int innerAccess = ByteArray.readU16bit(src, j + 6); + + if (innerClass != 0) + innerClass = cp.copy(innerClass, newCp, classnames); + + ByteArray.write16bit(innerClass, dest, j); + + if (outerClass != 0) + outerClass = cp.copy(outerClass, newCp, classnames); + + ByteArray.write16bit(outerClass, dest, j + 2); + + if (innerName != 0) + innerName = cp.copy(innerName, newCp, classnames); + + ByteArray.write16bit(innerName, dest, j + 4); + ByteArray.write16bit(innerAccess, dest, j + 6); + j += 8; + } + + return attr; + } +} diff --git a/src/main/javassist/bytecode/InstructionPrinter.java b/src/main/javassist/bytecode/InstructionPrinter.java new file mode 100644 index 0000000..f0a20e1 --- /dev/null +++ b/src/main/javassist/bytecode/InstructionPrinter.java @@ -0,0 +1,281 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba, and others. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ +package javassist.bytecode; + +import java.io.PrintStream; + +import javassist.CtMethod; + +/** + * Simple utility class for printing the instructions of a method. + * + * @author Jason T. Greene + */ +public class InstructionPrinter implements Opcode { + + private final static String opcodes[] = Mnemonic.OPCODE; + private final PrintStream stream; + + public InstructionPrinter(PrintStream stream) { + this.stream = stream; + } + + public static void print(CtMethod method, PrintStream stream) { + (new InstructionPrinter(stream)).print(method); + } + + public void print(CtMethod method) { + MethodInfo info = method.getMethodInfo2(); + ConstPool pool = info.getConstPool(); + CodeAttribute code = info.getCodeAttribute(); + if (code == null) + return; + + CodeIterator iterator = code.iterator(); + while (iterator.hasNext()) { + int pos; + try { + pos = iterator.next(); + } catch (BadBytecode e) { + throw new RuntimeException(e); + } + + stream.println(pos + ": " + instructionString(iterator, pos, pool)); + } + } + + public static String instructionString(CodeIterator iter, int pos, ConstPool pool) { + int opcode = iter.byteAt(pos); + + if (opcode > opcodes.length || opcode < 0) + throw new IllegalArgumentException("Invalid opcode, opcode: " + opcode + " pos: "+ pos); + + String opstring = opcodes[opcode]; + switch (opcode) { + case BIPUSH: + return opstring + " " + iter.byteAt(pos + 1); + case SIPUSH: + return opstring + " " + iter.s16bitAt(pos + 1); + case LDC: + return opstring + " " + ldc(pool, iter.byteAt(pos + 1)); + case LDC_W : + case LDC2_W : + return opstring + " " + ldc(pool, iter.u16bitAt(pos + 1)); + case ILOAD: + case LLOAD: + case FLOAD: + case DLOAD: + case ALOAD: + case ISTORE: + case LSTORE: + case FSTORE: + case DSTORE: + case ASTORE: + return opstring + " " + iter.byteAt(pos + 1); + case IFEQ: + case IFGE: + case IFGT: + case IFLE: + case IFLT: + case IFNE: + case IFNONNULL: + case IFNULL: + case IF_ACMPEQ: + case IF_ACMPNE: + case IF_ICMPEQ: + case IF_ICMPGE: + case IF_ICMPGT: + case IF_ICMPLE: + case IF_ICMPLT: + case IF_ICMPNE: + return opstring + " " + (iter.s16bitAt(pos + 1) + pos); + case IINC: + return opstring + " " + iter.byteAt(pos + 1); + case GOTO: + case JSR: + return opstring + " " + (iter.s16bitAt(pos + 1) + pos); + case RET: + return opstring + " " + iter.byteAt(pos + 1); + case TABLESWITCH: + return tableSwitch(iter, pos); + case LOOKUPSWITCH: + return lookupSwitch(iter, pos); + case GETSTATIC: + case PUTSTATIC: + case GETFIELD: + case PUTFIELD: + return opstring + " " + fieldInfo(pool, iter.u16bitAt(pos + 1)); + case INVOKEVIRTUAL: + case INVOKESPECIAL: + case INVOKESTATIC: + return opstring + " " + methodInfo(pool, iter.u16bitAt(pos + 1)); + case INVOKEINTERFACE: + return opstring + " " + interfaceMethodInfo(pool, iter.u16bitAt(pos + 1)); + case 186: + throw new RuntimeException("Bad opcode 186"); + case NEW: + return opstring + " " + classInfo(pool, iter.u16bitAt(pos + 1)); + case NEWARRAY: + return opstring + " " + arrayInfo(iter.byteAt(pos + 1)); + case ANEWARRAY: + case CHECKCAST: + return opstring + " " + classInfo(pool, iter.u16bitAt(pos + 1)); + case WIDE: + return wide(iter, pos); + case MULTIANEWARRAY: + return opstring + " " + classInfo(pool, iter.u16bitAt(pos + 1)); + case GOTO_W: + case JSR_W: + return opstring + " " + (iter.s32bitAt(pos + 1)+ pos); + default: + return opstring; + } + } + + + private static String wide(CodeIterator iter, int pos) { + int opcode = iter.byteAt(pos + 1); + int index = iter.u16bitAt(pos + 2); + switch (opcode) { + case ILOAD: + case LLOAD: + case FLOAD: + case DLOAD: + case ALOAD: + case ISTORE: + case LSTORE: + case FSTORE: + case DSTORE: + case ASTORE: + case IINC: + case RET: + return opcodes[opcode] + " " + index; + default: + throw new RuntimeException("Invalid WIDE operand"); + } + } + + + private static String arrayInfo(int type) { + switch (type) { + case T_BOOLEAN: + return "boolean"; + case T_CHAR: + return "char"; + case T_BYTE: + return "byte"; + case T_SHORT: + return "short"; + case T_INT: + return "int"; + case T_LONG: + return "long"; + case T_FLOAT: + return "float"; + case T_DOUBLE: + return "double"; + default: + throw new RuntimeException("Invalid array type"); + } + } + + + private static String classInfo(ConstPool pool, int index) { + return "#" + index + " = Class " + pool.getClassInfo(index); + } + + + private static String interfaceMethodInfo(ConstPool pool, int index) { + return "#" + index + " = Method " + + pool.getInterfaceMethodrefClassName(index) + "." + + pool.getInterfaceMethodrefName(index) + "(" + + pool.getInterfaceMethodrefType(index) + ")"; + } + + private static String methodInfo(ConstPool pool, int index) { + return "#" + index + " = Method " + + pool.getMethodrefClassName(index) + "." + + pool.getMethodrefName(index) + "(" + + pool.getMethodrefType(index) + ")"; + } + + + private static String fieldInfo(ConstPool pool, int index) { + return "#" + index + " = Field " + + pool.getFieldrefClassName(index) + "." + + pool.getFieldrefName(index) + "(" + + pool.getFieldrefType(index) + ")"; + } + + + private static String lookupSwitch(CodeIterator iter, int pos) { + StringBuffer buffer = new StringBuffer("lookupswitch {\n"); + int index = (pos & ~3) + 4; + // default + buffer.append("\t\tdefault: ").append(pos + iter.s32bitAt(index)).append("\n"); + int npairs = iter.s32bitAt(index += 4); + int end = npairs * 8 + (index += 4); + + for (; index < end; index += 8) { + int match = iter.s32bitAt(index); + int target = iter.s32bitAt(index + 4) + pos; + buffer.append("\t\t").append(match).append(": ").append(target).append("\n"); + } + + buffer.setCharAt(buffer.length() - 1, '}'); + return buffer.toString(); + } + + + private static String tableSwitch(CodeIterator iter, int pos) { + StringBuffer buffer = new StringBuffer("tableswitch {\n"); + int index = (pos & ~3) + 4; + // default + buffer.append("\t\tdefault: ").append(pos + iter.s32bitAt(index)).append("\n"); + int low = iter.s32bitAt(index += 4); + int high = iter.s32bitAt(index += 4); + int end = (high - low + 1) * 4 + (index += 4); + + // Offset table + for (int key = low; index < end; index += 4, key++) { + int target = iter.s32bitAt(index) + pos; + buffer.append("\t\t").append(key).append(": ").append(target).append("\n"); + } + + buffer.setCharAt(buffer.length() - 1, '}'); + return buffer.toString(); + } + + + private static String ldc(ConstPool pool, int index) { + int tag = pool.getTag(index); + switch (tag) { + case ConstPool.CONST_String: + return "#" + index + " = \"" + pool.getStringInfo(index) + "\""; + case ConstPool.CONST_Integer: + return "#" + index + " = int " + pool.getIntegerInfo(index); + case ConstPool.CONST_Float: + return "#" + index + " = float " + pool.getFloatInfo(index); + case ConstPool.CONST_Long: + return "#" + index + " = long " + pool.getLongInfo(index); + case ConstPool.CONST_Double: + return "#" + index + " = int " + pool.getDoubleInfo(index); + case ConstPool.CONST_Class: + return classInfo(pool, index); + default: + throw new RuntimeException("bad LDC: " + tag); + } + } +} diff --git a/src/main/javassist/bytecode/LineNumberAttribute.java b/src/main/javassist/bytecode/LineNumberAttribute.java new file mode 100644 index 0000000..f384d2f --- /dev/null +++ b/src/main/javassist/bytecode/LineNumberAttribute.java @@ -0,0 +1,181 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.bytecode; + +import java.io.DataInputStream; +import java.io.IOException; +import java.util.Map; + +/** + * <code>LineNumberTable_attribute</code>. + */ +public class LineNumberAttribute extends AttributeInfo { + /** + * The name of this attribute <code>"LineNumberTable"</code>. + */ + public static final String tag = "LineNumberTable"; + + LineNumberAttribute(ConstPool cp, int n, DataInputStream in) + throws IOException + { + super(cp, n, in); + } + + private LineNumberAttribute(ConstPool cp, byte[] i) { + super(cp, tag, i); + } + + /** + * Returns <code>line_number_table_length</code>. + * This represents the number of entries in the table. + */ + public int tableLength() { + return ByteArray.readU16bit(info, 0); + } + + /** + * Returns <code>line_number_table[i].start_pc</code>. + * This represents the index into the code array at which the code + * for a new line in the original source file begins. + * + * @param i the i-th entry. + */ + public int startPc(int i) { + return ByteArray.readU16bit(info, i * 4 + 2); + } + + /** + * Returns <code>line_number_table[i].line_number</code>. + * This represents the corresponding line number in the original + * source file. + * + * @param i the i-th entry. + */ + public int lineNumber(int i) { + return ByteArray.readU16bit(info, i * 4 + 4); + } + + /** + * Returns the line number corresponding to the specified bytecode. + * + * @param pc the index into the code array. + */ + public int toLineNumber(int pc) { + int n = tableLength(); + int i = 0; + for (; i < n; ++i) + if (pc < startPc(i)) + if (i == 0) + return lineNumber(0); + else + break; + + return lineNumber(i - 1); + } + + /** + * Returns the index into the code array at which the code for + * the specified line begins. + * + * @param line the line number. + * @return -1 if the specified line is not found. + */ + public int toStartPc(int line) { + int n = tableLength(); + for (int i = 0; i < n; ++i) + if (line == lineNumber(i)) + return startPc(i); + + return -1; + } + + /** + * Used as a return type of <code>toNearPc()</code>. + */ + static public class Pc { + /** + * The index into the code array. + */ + public int index; + /** + * The line number. + */ + public int line; + } + + /** + * Returns the index into the code array at which the code for + * the specified line (or the nearest line after the specified one) + * begins. + * + * @param line the line number. + * @return a pair of the index and the line number of the + * bytecode at that index. + */ + public Pc toNearPc(int line) { + int n = tableLength(); + int nearPc = 0; + int distance = 0; + if (n > 0) { + distance = lineNumber(0) - line; + nearPc = startPc(0); + } + + for (int i = 1; i < n; ++i) { + int d = lineNumber(i) - line; + if ((d < 0 && d > distance) + || (d >= 0 && (d < distance || distance < 0))) { + distance = d; + nearPc = startPc(i); + } + } + + Pc res = new Pc(); + res.index = nearPc; + res.line = line + distance; + return res; + } + + /** + * Makes a copy. + * + * @param newCp the constant pool table used by the new copy. + * @param classnames should be null. + */ + public AttributeInfo copy(ConstPool newCp, Map classnames) { + byte[] src = info; + int num = src.length; + byte[] dest = new byte[num]; + for (int i = 0; i < num; ++i) + dest[i] = src[i]; + + LineNumberAttribute attr = new LineNumberAttribute(newCp, dest); + return attr; + } + + /** + * Adjusts start_pc if bytecode is inserted in a method body. + */ + void shiftPc(int where, int gapLength, boolean exclusive) { + int n = tableLength(); + for (int i = 0; i < n; ++i) { + int pos = i * 4 + 2; + int pc = ByteArray.readU16bit(info, pos); + if (pc > where || (exclusive && pc == where)) + ByteArray.write16bit(pc + gapLength, info, pos); + } + } +} diff --git a/src/main/javassist/bytecode/LocalVariableAttribute.java b/src/main/javassist/bytecode/LocalVariableAttribute.java new file mode 100644 index 0000000..3d44a29 --- /dev/null +++ b/src/main/javassist/bytecode/LocalVariableAttribute.java @@ -0,0 +1,333 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.bytecode; + +import java.io.DataInputStream; +import java.io.IOException; +import java.util.Map; + +/** + * <code>LocalVariableTable_attribute</code>. + */ +public class LocalVariableAttribute extends AttributeInfo { + /** + * The name of this attribute <code>"LocalVariableTable"</code>. + */ + public static final String tag = "LocalVariableTable"; + + /** + * The name of the attribute <code>"LocalVariableTypeTable"</code>. + */ + public static final String typeTag = "LocalVariableTypeTable"; + + /** + * Constructs an empty LocalVariableTable. + */ + public LocalVariableAttribute(ConstPool cp) { + super(cp, tag, new byte[2]); + ByteArray.write16bit(0, info, 0); + } + + /** + * Constructs an empty LocalVariableTable. + * + * @param name the attribute name. + * <code>LocalVariableAttribute.tag</code> or + * <code>LocalVariableAttribute.typeTag</code>. + * @see #tag + * @see #typeTag + * @since 3.1 + * @deprecated + */ + public LocalVariableAttribute(ConstPool cp, String name) { + super(cp, name, new byte[2]); + ByteArray.write16bit(0, info, 0); + } + + LocalVariableAttribute(ConstPool cp, int n, DataInputStream in) + throws IOException + { + super(cp, n, in); + } + + LocalVariableAttribute(ConstPool cp, String name, byte[] i) { + super(cp, name, i); + } + + /** + * Appends a new entry to <code>local_variable_table</code>. + * + * @param startPc <code>start_pc</code> + * @param length <code>length</code> + * @param nameIndex <code>name_index</code> + * @param descriptorIndex <code>descriptor_index</code> + * @param index <code>index</code> + */ + public void addEntry(int startPc, int length, int nameIndex, + int descriptorIndex, int index) { + int size = info.length; + byte[] newInfo = new byte[size + 10]; + ByteArray.write16bit(tableLength() + 1, newInfo, 0); + for (int i = 2; i < size; ++i) + newInfo[i] = info[i]; + + ByteArray.write16bit(startPc, newInfo, size); + ByteArray.write16bit(length, newInfo, size + 2); + ByteArray.write16bit(nameIndex, newInfo, size + 4); + ByteArray.write16bit(descriptorIndex, newInfo, size + 6); + ByteArray.write16bit(index, newInfo, size + 8); + info = newInfo; + } + + void renameClass(String oldname, String newname) { + ConstPool cp = getConstPool(); + int n = tableLength(); + for (int i = 0; i < n; ++i) { + int pos = i * 10 + 2; + int index = ByteArray.readU16bit(info, pos + 6); + if (index != 0) { + String desc = cp.getUtf8Info(index); + desc = renameEntry(desc, oldname, newname); + ByteArray.write16bit(cp.addUtf8Info(desc), info, pos + 6); + } + } + } + + String renameEntry(String desc, String oldname, String newname) { + return Descriptor.rename(desc, oldname, newname); + } + + void renameClass(Map classnames) { + ConstPool cp = getConstPool(); + int n = tableLength(); + for (int i = 0; i < n; ++i) { + int pos = i * 10 + 2; + int index = ByteArray.readU16bit(info, pos + 6); + if (index != 0) { + String desc = cp.getUtf8Info(index); + desc = renameEntry(desc, classnames); + ByteArray.write16bit(cp.addUtf8Info(desc), info, pos + 6); + } + } + } + + String renameEntry(String desc, Map classnames) { + return Descriptor.rename(desc, classnames); + } + + /** + * For each <code>local_variable_table[i].index</code>, + * this method increases <code>index</code> by <code>delta</code>. + * + * @param lessThan the index does not change if it + * is less than this value. + */ + public void shiftIndex(int lessThan, int delta) { + int size = info.length; + for (int i = 2; i < size; i += 10){ + int org = ByteArray.readU16bit(info, i + 8); + if (org >= lessThan) + ByteArray.write16bit(org + delta, info, i + 8); + } + } + + /** + * Returns <code>local_variable_table_length</code>. + * This represents the number of entries in the table. + */ + public int tableLength() { + return ByteArray.readU16bit(info, 0); + } + + /** + * Returns <code>local_variable_table[i].start_pc</code>. + * This represents the index into the code array from which the local + * variable is effective. + * + * @param i the i-th entry. + */ + public int startPc(int i) { + return ByteArray.readU16bit(info, i * 10 + 2); + } + + /** + * Returns <code>local_variable_table[i].length</code>. + * This represents the length of the code region in which the local + * variable is effective. + * + * @param i the i-th entry. + */ + public int codeLength(int i) { + return ByteArray.readU16bit(info, i * 10 + 4); + } + + /** + * Adjusts start_pc and length if bytecode is inserted in a method body. + */ + void shiftPc(int where, int gapLength, boolean exclusive) { + int n = tableLength(); + for (int i = 0; i < n; ++i) { + int pos = i * 10 + 2; + int pc = ByteArray.readU16bit(info, pos); + int len = ByteArray.readU16bit(info, pos + 2); + + /* if pc == 0, then the local variable is a method parameter. + */ + if (pc > where || (exclusive && pc == where && pc != 0)) + ByteArray.write16bit(pc + gapLength, info, pos); + else if (pc + len > where || (exclusive && pc + len == where)) + ByteArray.write16bit(len + gapLength, info, pos + 2); + } + } + + /** + * Returns the value of <code>local_variable_table[i].name_index</code>. + * This represents the name of the local variable. + * + * @param i the i-th entry. + */ + public int nameIndex(int i) { + return ByteArray.readU16bit(info, i * 10 + 6); + } + + /** + * Returns the name of the local variable + * specified by <code>local_variable_table[i].name_index</code>. + * + * @param i the i-th entry. + */ + public String variableName(int i) { + return getConstPool().getUtf8Info(nameIndex(i)); + } + + /** + * Returns the value of + * <code>local_variable_table[i].descriptor_index</code>. + * This represents the type descriptor of the local variable. + * <p> + * If this attribute represents a LocalVariableTypeTable attribute, + * this method returns the value of + * <code>local_variable_type_table[i].signature_index</code>. + * It represents the type of the local variable. + * + * @param i the i-th entry. + */ + public int descriptorIndex(int i) { + return ByteArray.readU16bit(info, i * 10 + 8); + } + + /** + * This method is equivalent to <code>descriptorIndex()</code>. + * If this attribute represents a LocalVariableTypeTable attribute, + * this method should be used instead of <code>descriptorIndex()</code> + * since the method name is more appropriate. + * + * @param i the i-th entry. + * @see #descriptorIndex(int) + * @see SignatureAttribute#toFieldSignature(String) + */ + public int signatureIndex(int i) { + return descriptorIndex(i); + } + + /** + * Returns the type descriptor of the local variable + * specified by <code>local_variable_table[i].descriptor_index</code>. + * <p> + * If this attribute represents a LocalVariableTypeTable attribute, + * this method returns the type signature of the local variable + * specified by <code>local_variable_type_table[i].signature_index</code>. + * + * @param i the i-th entry. + */ + public String descriptor(int i) { + return getConstPool().getUtf8Info(descriptorIndex(i)); + } + + /** + * This method is equivalent to <code>descriptor()</code>. + * If this attribute represents a LocalVariableTypeTable attribute, + * this method should be used instead of <code>descriptor()</code> + * since the method name is more appropriate. + * + * <p>To parse the string, call <code>toFieldSignature(String)</code> + * in <code>SignatureAttribute</code>. + * + * @param i the i-th entry. + * @see #descriptor(int) + * @see SignatureAttribute#toFieldSignature(String) + */ + public String signature(int i) { + return descriptor(i); + } + + /** + * Returns <code>local_variable_table[i].index</code>. + * This represents the index of the local variable. + * + * @param i the i-th entry. + */ + public int index(int i) { + return ByteArray.readU16bit(info, i * 10 + 10); + } + + /** + * Makes a copy. + * + * @param newCp the constant pool table used by the new copy. + * @param classnames should be null. + */ + public AttributeInfo copy(ConstPool newCp, Map classnames) { + byte[] src = get(); + byte[] dest = new byte[src.length]; + ConstPool cp = getConstPool(); + LocalVariableAttribute attr = makeThisAttr(newCp, dest); + int n = ByteArray.readU16bit(src, 0); + ByteArray.write16bit(n, dest, 0); + int j = 2; + for (int i = 0; i < n; ++i) { + int start = ByteArray.readU16bit(src, j); + int len = ByteArray.readU16bit(src, j + 2); + int name = ByteArray.readU16bit(src, j + 4); + int type = ByteArray.readU16bit(src, j + 6); + int index = ByteArray.readU16bit(src, j + 8); + + ByteArray.write16bit(start, dest, j); + ByteArray.write16bit(len, dest, j + 2); + if (name != 0) + name = cp.copy(name, newCp, null); + + ByteArray.write16bit(name, dest, j + 4); + + if (type != 0) { + String sig = cp.getUtf8Info(type); + sig = Descriptor.rename(sig, classnames); + type = newCp.addUtf8Info(sig); + } + + ByteArray.write16bit(type, dest, j + 6); + ByteArray.write16bit(index, dest, j + 8); + j += 10; + } + + return attr; + } + + // LocalVariableTypeAttribute overrides this method. + LocalVariableAttribute makeThisAttr(ConstPool cp, byte[] dest) { + return new LocalVariableAttribute(cp, tag, dest); + } +} diff --git a/src/main/javassist/bytecode/LocalVariableTypeAttribute.java b/src/main/javassist/bytecode/LocalVariableTypeAttribute.java new file mode 100644 index 0000000..d7ac098 --- /dev/null +++ b/src/main/javassist/bytecode/LocalVariableTypeAttribute.java @@ -0,0 +1,62 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.bytecode; + +import java.io.DataInputStream; +import java.io.IOException; +import java.util.Map; + +/** + * <code>LocalVariableTypeTable_attribute</code>. + * + * @since 3.11 + */ +public class LocalVariableTypeAttribute extends LocalVariableAttribute { + /** + * The name of the attribute <code>"LocalVariableTypeTable"</code>. + */ + public static final String tag = LocalVariableAttribute.typeTag; + + /** + * Constructs an empty LocalVariableTypeTable. + */ + public LocalVariableTypeAttribute(ConstPool cp) { + super(cp, tag, new byte[2]); + ByteArray.write16bit(0, info, 0); + } + + LocalVariableTypeAttribute(ConstPool cp, int n, DataInputStream in) + throws IOException + { + super(cp, n, in); + } + + private LocalVariableTypeAttribute(ConstPool cp, byte[] dest) { + super(cp, tag, dest); + } + + String renameEntry(String desc, String oldname, String newname) { + return SignatureAttribute.renameClass(desc, oldname, newname); + } + + String renameEntry(String desc, Map classnames) { + return SignatureAttribute.renameClass(desc, classnames); + } + + LocalVariableAttribute makeThisAttr(ConstPool cp, byte[] dest) { + return new LocalVariableTypeAttribute(cp, dest); + } +} diff --git a/src/main/javassist/bytecode/LongVector.java b/src/main/javassist/bytecode/LongVector.java new file mode 100644 index 0000000..1f76b4a --- /dev/null +++ b/src/main/javassist/bytecode/LongVector.java @@ -0,0 +1,63 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.bytecode; + +final class LongVector { + static final int ASIZE = 128; + static final int ABITS = 7; // ASIZE = 2^ABITS + static final int VSIZE = 8; + private ConstInfo[][] objects; + private int elements; + + public LongVector() { + objects = new ConstInfo[VSIZE][]; + elements = 0; + } + + public LongVector(int initialSize) { + int vsize = ((initialSize >> ABITS) & ~(VSIZE - 1)) + VSIZE; + objects = new ConstInfo[vsize][]; + elements = 0; + } + + public int size() { return elements; } + + public int capacity() { return objects.length * ASIZE; } + + public ConstInfo elementAt(int i) { + if (i < 0 || elements <= i) + return null; + + return objects[i >> ABITS][i & (ASIZE - 1)]; + } + + public void addElement(ConstInfo value) { + int nth = elements >> ABITS; + int offset = elements & (ASIZE - 1); + int len = objects.length; + if (nth >= len) { + ConstInfo[][] newObj = new ConstInfo[len + VSIZE][]; + System.arraycopy(objects, 0, newObj, 0, len); + objects = newObj; + } + + if (objects[nth] == null) + objects[nth] = new ConstInfo[ASIZE]; + + objects[nth][offset] = value; + elements++; + } +} diff --git a/src/main/javassist/bytecode/MethodInfo.java b/src/main/javassist/bytecode/MethodInfo.java new file mode 100644 index 0000000..aae98ea --- /dev/null +++ b/src/main/javassist/bytecode/MethodInfo.java @@ -0,0 +1,542 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.bytecode; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import javassist.ClassPool; +import javassist.bytecode.stackmap.MapMaker; + +/** + * <code>method_info</code> structure. + * + * @see javassist.CtMethod#getMethodInfo() + * @see javassist.CtConstructor#getMethodInfo() + */ +public class MethodInfo { + ConstPool constPool; + int accessFlags; + int name; + String cachedName; + int descriptor; + ArrayList attribute; // may be null + + /** + * If this value is true, Javassist maintains a <code>StackMap</code> attribute + * generated by the <code>preverify</code> tool of J2ME (CLDC). The initial + * value of this field is <code>false</code>. + */ + public static boolean doPreverify = false; + + /** + * The name of constructors: <code><init></code>. + */ + public static final String nameInit = "<init>"; + + /** + * The name of class initializer (static initializer): + * <code><clinit></code>. + */ + public static final String nameClinit = "<clinit>"; + + private MethodInfo(ConstPool cp) { + constPool = cp; + attribute = null; + } + + /** + * Constructs a <code>method_info</code> structure. The initial value of + * <code>access_flags</code> is zero. + * + * @param cp + * a constant pool table + * @param methodname + * method name + * @param desc + * method descriptor + * @see Descriptor + */ + public MethodInfo(ConstPool cp, String methodname, String desc) { + this(cp); + accessFlags = 0; + name = cp.addUtf8Info(methodname); + cachedName = methodname; + descriptor = constPool.addUtf8Info(desc); + } + + MethodInfo(ConstPool cp, DataInputStream in) throws IOException { + this(cp); + read(in); + } + + /** + * Constructs a copy of <code>method_info</code> structure. Class names + * appearing in the source <code>method_info</code> are renamed according + * to <code>classnameMap</code>. + * + * <p> + * Note: only <code>Code</code> and <code>Exceptions</code> attributes + * are copied from the source. The other attributes are ignored. + * + * @param cp + * a constant pool table + * @param methodname + * a method name + * @param src + * a source <code>method_info</code> + * @param classnameMap + * specifies pairs of replaced and substituted name. + * @see Descriptor + */ + public MethodInfo(ConstPool cp, String methodname, MethodInfo src, + Map classnameMap) throws BadBytecode { + this(cp); + read(src, methodname, classnameMap); + } + + /** + * Returns a string representation of the object. + */ + public String toString() { + return getName() + " " + getDescriptor(); + } + + /** + * Copies all constant pool items to a given new constant pool + * and replaces the original items with the new ones. + * This is used for garbage collecting the items of removed fields + * and methods. + * + * @param cp the destination + */ + void compact(ConstPool cp) { + name = cp.addUtf8Info(getName()); + descriptor = cp.addUtf8Info(getDescriptor()); + attribute = AttributeInfo.copyAll(attribute, cp); + constPool = cp; + } + + void prune(ConstPool cp) { + ArrayList newAttributes = new ArrayList(); + + AttributeInfo invisibleAnnotations + = getAttribute(AnnotationsAttribute.invisibleTag); + if (invisibleAnnotations != null) { + invisibleAnnotations = invisibleAnnotations.copy(cp, null); + newAttributes.add(invisibleAnnotations); + } + + AttributeInfo visibleAnnotations + = getAttribute(AnnotationsAttribute.visibleTag); + if (visibleAnnotations != null) { + visibleAnnotations = visibleAnnotations.copy(cp, null); + newAttributes.add(visibleAnnotations); + } + + AttributeInfo parameterInvisibleAnnotations + = getAttribute(ParameterAnnotationsAttribute.invisibleTag); + if (parameterInvisibleAnnotations != null) { + parameterInvisibleAnnotations = parameterInvisibleAnnotations.copy(cp, null); + newAttributes.add(parameterInvisibleAnnotations); + } + + AttributeInfo parameterVisibleAnnotations + = getAttribute(ParameterAnnotationsAttribute.visibleTag); + if (parameterVisibleAnnotations != null) { + parameterVisibleAnnotations = parameterVisibleAnnotations.copy(cp, null); + newAttributes.add(parameterVisibleAnnotations); + } + + AnnotationDefaultAttribute defaultAttribute + = (AnnotationDefaultAttribute) getAttribute(AnnotationDefaultAttribute.tag); + if (defaultAttribute != null) + newAttributes.add(defaultAttribute); + + ExceptionsAttribute ea = getExceptionsAttribute(); + if (ea != null) + newAttributes.add(ea); + + AttributeInfo signature + = getAttribute(SignatureAttribute.tag); + if (signature != null) { + signature = signature.copy(cp, null); + newAttributes.add(signature); + } + + attribute = newAttributes; + name = cp.addUtf8Info(getName()); + descriptor = cp.addUtf8Info(getDescriptor()); + constPool = cp; + } + + /** + * Returns a method name. + */ + public String getName() { + if (cachedName == null) + cachedName = constPool.getUtf8Info(name); + + return cachedName; + } + + /** + * Sets a method name. + */ + public void setName(String newName) { + name = constPool.addUtf8Info(newName); + cachedName = newName; + } + + /** + * Returns true if this is not a constructor or a class initializer (static + * initializer). + */ + public boolean isMethod() { + String n = getName(); + return !n.equals(nameInit) && !n.equals(nameClinit); + } + + /** + * Returns a constant pool table used by this method. + */ + public ConstPool getConstPool() { + return constPool; + } + + /** + * Returns true if this is a constructor. + */ + public boolean isConstructor() { + return getName().equals(nameInit); + } + + /** + * Returns true if this is a class initializer (static initializer). + */ + public boolean isStaticInitializer() { + return getName().equals(nameClinit); + } + + /** + * Returns access flags. + * + * @see AccessFlag + */ + public int getAccessFlags() { + return accessFlags; + } + + /** + * Sets access flags. + * + * @see AccessFlag + */ + public void setAccessFlags(int acc) { + accessFlags = acc; + } + + /** + * Returns a method descriptor. + * + * @see Descriptor + */ + public String getDescriptor() { + return constPool.getUtf8Info(descriptor); + } + + /** + * Sets a method descriptor. + * + * @see Descriptor + */ + public void setDescriptor(String desc) { + if (!desc.equals(getDescriptor())) + descriptor = constPool.addUtf8Info(desc); + } + + /** + * Returns all the attributes. The returned <code>List</code> object + * is shared with this object. If you add a new attribute to the list, + * the attribute is also added to the method represented by this + * object. If you remove an attribute from the list, it is also removed + * from the method. + * + * @return a list of <code>AttributeInfo</code> objects. + * @see AttributeInfo + */ + public List getAttributes() { + if (attribute == null) + attribute = new ArrayList(); + + return attribute; + } + + /** + * Returns the attribute with the specified name. If it is not found, this + * method returns null. + * + * @param name attribute name + * @return an <code>AttributeInfo</code> object or null. + * @see #getAttributes() + */ + public AttributeInfo getAttribute(String name) { + return AttributeInfo.lookup(attribute, name); + } + + /** + * Appends an attribute. If there is already an attribute with the same + * name, the new one substitutes for it. + * + * @see #getAttributes() + */ + public void addAttribute(AttributeInfo info) { + if (attribute == null) + attribute = new ArrayList(); + + AttributeInfo.remove(attribute, info.getName()); + attribute.add(info); + } + + /** + * Returns an Exceptions attribute. + * + * @return an Exceptions attribute or null if it is not specified. + */ + public ExceptionsAttribute getExceptionsAttribute() { + AttributeInfo info = AttributeInfo.lookup(attribute, + ExceptionsAttribute.tag); + return (ExceptionsAttribute)info; + } + + /** + * Returns a Code attribute. + * + * @return a Code attribute or null if it is not specified. + */ + public CodeAttribute getCodeAttribute() { + AttributeInfo info = AttributeInfo.lookup(attribute, CodeAttribute.tag); + return (CodeAttribute)info; + } + + /** + * Removes an Exception attribute. + */ + public void removeExceptionsAttribute() { + AttributeInfo.remove(attribute, ExceptionsAttribute.tag); + } + + /** + * Adds an Exception attribute. + * + * <p> + * The added attribute must share the same constant pool table as this + * <code>method_info</code> structure. + */ + public void setExceptionsAttribute(ExceptionsAttribute cattr) { + removeExceptionsAttribute(); + if (attribute == null) + attribute = new ArrayList(); + + attribute.add(cattr); + } + + /** + * Removes a Code attribute. + */ + public void removeCodeAttribute() { + AttributeInfo.remove(attribute, CodeAttribute.tag); + } + + /** + * Adds a Code attribute. + * + * <p> + * The added attribute must share the same constant pool table as this + * <code>method_info</code> structure. + */ + public void setCodeAttribute(CodeAttribute cattr) { + removeCodeAttribute(); + if (attribute == null) + attribute = new ArrayList(); + + attribute.add(cattr); + } + + /** + * Rebuilds a stack map table if the class file is for Java 6 + * or later. Java 5 or older Java VMs do not recognize a stack + * map table. If <code>doPreverify</code> is true, this method + * also rebuilds a stack map for J2ME (CLDC). + * + * @param pool used for making type hierarchy. + * @param cf rebuild if this class file is for Java 6 or later. + * @see #rebuildStackMap(ClassPool) + * @see #rebuildStackMapForME(ClassPool) + * @since 3.6 + */ + public void rebuildStackMapIf6(ClassPool pool, ClassFile cf) + throws BadBytecode + { + if (cf.getMajorVersion() >= ClassFile.JAVA_6) + rebuildStackMap(pool); + + if (doPreverify) + rebuildStackMapForME(pool); + } + + /** + * Rebuilds a stack map table. If no stack map table is included, + * a new one is created. If this <code>MethodInfo</code> does not + * include a code attribute, nothing happens. + * + * @param pool used for making type hierarchy. + * @see StackMapTable + * @since 3.6 + */ + public void rebuildStackMap(ClassPool pool) throws BadBytecode { + CodeAttribute ca = getCodeAttribute(); + if (ca != null) { + StackMapTable smt = MapMaker.make(pool, this); + ca.setAttribute(smt); + } + } + + /** + * Rebuilds a stack map table for J2ME (CLDC). If no stack map table is included, + * a new one is created. If this <code>MethodInfo</code> does not + * include a code attribute, nothing happens. + * + * @param pool used for making type hierarchy. + * @see StackMapTable + * @since 3.12 + */ + public void rebuildStackMapForME(ClassPool pool) throws BadBytecode { + CodeAttribute ca = getCodeAttribute(); + if (ca != null) { + StackMap sm = MapMaker.make2(pool, this); + ca.setAttribute(sm); + } + } + + /** + * Returns the line number of the source line corresponding to the specified + * bytecode contained in this method. + * + * @param pos + * the position of the bytecode (>= 0). an index into the code + * array. + * @return -1 if this information is not available. + */ + public int getLineNumber(int pos) { + CodeAttribute ca = getCodeAttribute(); + if (ca == null) + return -1; + + LineNumberAttribute ainfo = (LineNumberAttribute)ca + .getAttribute(LineNumberAttribute.tag); + if (ainfo == null) + return -1; + + return ainfo.toLineNumber(pos); + } + + /** + * Changes a super constructor called by this constructor. + * + * <p> + * This method modifies a call to <code>super()</code>, which should be + * at the head of a constructor body, so that a constructor in a different + * super class is called. This method does not change actual parameters. + * Hence the new super class must have a constructor with the same signature + * as the original one. + * + * <p> + * This method should be called when the super class of the class declaring + * this method is changed. + * + * <p> + * This method does not perform anything unless this <code>MethodInfo</code> + * represents a constructor. + * + * @param superclass + * the new super class + */ + public void setSuperclass(String superclass) throws BadBytecode { + if (!isConstructor()) + return; + + CodeAttribute ca = getCodeAttribute(); + byte[] code = ca.getCode(); + CodeIterator iterator = ca.iterator(); + int pos = iterator.skipSuperConstructor(); + if (pos >= 0) { // not this() + ConstPool cp = constPool; + int mref = ByteArray.readU16bit(code, pos + 1); + int nt = cp.getMethodrefNameAndType(mref); + int sc = cp.addClassInfo(superclass); + int mref2 = cp.addMethodrefInfo(sc, nt); + ByteArray.write16bit(mref2, code, pos + 1); + } + } + + private void read(MethodInfo src, String methodname, Map classnames) + throws BadBytecode { + ConstPool destCp = constPool; + accessFlags = src.accessFlags; + name = destCp.addUtf8Info(methodname); + cachedName = methodname; + ConstPool srcCp = src.constPool; + String desc = srcCp.getUtf8Info(src.descriptor); + String desc2 = Descriptor.rename(desc, classnames); + descriptor = destCp.addUtf8Info(desc2); + + attribute = new ArrayList(); + ExceptionsAttribute eattr = src.getExceptionsAttribute(); + if (eattr != null) + attribute.add(eattr.copy(destCp, classnames)); + + CodeAttribute cattr = src.getCodeAttribute(); + if (cattr != null) + attribute.add(cattr.copy(destCp, classnames)); + } + + private void read(DataInputStream in) throws IOException { + accessFlags = in.readUnsignedShort(); + name = in.readUnsignedShort(); + descriptor = in.readUnsignedShort(); + int n = in.readUnsignedShort(); + attribute = new ArrayList(); + for (int i = 0; i < n; ++i) + attribute.add(AttributeInfo.read(constPool, in)); + } + + void write(DataOutputStream out) throws IOException { + out.writeShort(accessFlags); + out.writeShort(name); + out.writeShort(descriptor); + + if (attribute == null) + out.writeShort(0); + else { + out.writeShort(attribute.size()); + AttributeInfo.writeAll(attribute, out); + } + } +} diff --git a/src/main/javassist/bytecode/Mnemonic.java b/src/main/javassist/bytecode/Mnemonic.java new file mode 100644 index 0000000..2eb596a --- /dev/null +++ b/src/main/javassist/bytecode/Mnemonic.java @@ -0,0 +1,241 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.bytecode; + +/** + * JVM Instruction Names. + * + * <p>This interface has been separated from javassist.bytecode.Opcode + * because typical bytecode translators do not use mnemonics. If this + * interface were merged with Opcode, extra memory would be unnecessary + * consumed. + * + * @see Opcode + */ +public interface Mnemonic { + + /** + * The instruction names (mnemonics) sorted by the opcode. + * The length of this array is 202 (jsr_w=201). + * + * <p>The value at index 186 is null since no instruction is + * assigned to 186. + */ + String[] OPCODE = { + "nop", /* 0*/ + "aconst_null", /* 1*/ + "iconst_m1", /* 2*/ + "iconst_0", /* 3*/ + "iconst_1", /* 4*/ + "iconst_2", /* 5*/ + "iconst_3", /* 6*/ + "iconst_4", /* 7*/ + "iconst_5", /* 8*/ + "lconst_0", /* 9*/ + "lconst_1", /* 10*/ + "fconst_0", /* 11*/ + "fconst_1", /* 12*/ + "fconst_2", /* 13*/ + "dconst_0", /* 14*/ + "dconst_1", /* 15*/ + "bipush", /* 16*/ + "sipush", /* 17*/ + "ldc", /* 18*/ + "ldc_w", /* 19*/ + "ldc2_w", /* 20*/ + "iload", /* 21*/ + "lload", /* 22*/ + "fload", /* 23*/ + "dload", /* 24*/ + "aload", /* 25*/ + "iload_0", /* 26*/ + "iload_1", /* 27*/ + "iload_2", /* 28*/ + "iload_3", /* 29*/ + "lload_0", /* 30*/ + "lload_1", /* 31*/ + "lload_2", /* 32*/ + "lload_3", /* 33*/ + "fload_0", /* 34*/ + "fload_1", /* 35*/ + "fload_2", /* 36*/ + "fload_3", /* 37*/ + "dload_0", /* 38*/ + "dload_1", /* 39*/ + "dload_2", /* 40*/ + "dload_3", /* 41*/ + "aload_0", /* 42*/ + "aload_1", /* 43*/ + "aload_2", /* 44*/ + "aload_3", /* 45*/ + "iaload", /* 46*/ + "laload", /* 47*/ + "faload", /* 48*/ + "daload", /* 49*/ + "aaload", /* 50*/ + "baload", /* 51*/ + "caload", /* 52*/ + "saload", /* 53*/ + "istore", /* 54*/ + "lstore", /* 55*/ + "fstore", /* 56*/ + "dstore", /* 57*/ + "astore", /* 58*/ + "istore_0", /* 59*/ + "istore_1", /* 60*/ + "istore_2", /* 61*/ + "istore_3", /* 62*/ + "lstore_0", /* 63*/ + "lstore_1", /* 64*/ + "lstore_2", /* 65*/ + "lstore_3", /* 66*/ + "fstore_0", /* 67*/ + "fstore_1", /* 68*/ + "fstore_2", /* 69*/ + "fstore_3", /* 70*/ + "dstore_0", /* 71*/ + "dstore_1", /* 72*/ + "dstore_2", /* 73*/ + "dstore_3", /* 74*/ + "astore_0", /* 75*/ + "astore_1", /* 76*/ + "astore_2", /* 77*/ + "astore_3", /* 78*/ + "iastore", /* 79*/ + "lastore", /* 80*/ + "fastore", /* 81*/ + "dastore", /* 82*/ + "aastore", /* 83*/ + "bastore", /* 84*/ + "castore", /* 85*/ + "sastore", /* 86*/ + "pop", /* 87*/ + "pop2", /* 88*/ + "dup", /* 89*/ + "dup_x1", /* 90*/ + "dup_x2", /* 91*/ + "dup2", /* 92*/ + "dup2_x1", /* 93*/ + "dup2_x2", /* 94*/ + "swap", /* 95*/ + "iadd", /* 96*/ + "ladd", /* 97*/ + "fadd", /* 98*/ + "dadd", /* 99*/ + "isub", /* 100*/ + "lsub", /* 101*/ + "fsub", /* 102*/ + "dsub", /* 103*/ + "imul", /* 104*/ + "lmul", /* 105*/ + "fmul", /* 106*/ + "dmul", /* 107*/ + "idiv", /* 108*/ + "ldiv", /* 109*/ + "fdiv", /* 110*/ + "ddiv", /* 111*/ + "irem", /* 112*/ + "lrem", /* 113*/ + "frem", /* 114*/ + "drem", /* 115*/ + "ineg", /* 116*/ + "lneg", /* 117*/ + "fneg", /* 118*/ + "dneg", /* 119*/ + "ishl", /* 120*/ + "lshl", /* 121*/ + "ishr", /* 122*/ + "lshr", /* 123*/ + "iushr", /* 124*/ + "lushr", /* 125*/ + "iand", /* 126*/ + "land", /* 127*/ + "ior", /* 128*/ + "lor", /* 129*/ + "ixor", /* 130*/ + "lxor", /* 131*/ + "iinc", /* 132*/ + "i2l", /* 133*/ + "i2f", /* 134*/ + "i2d", /* 135*/ + "l2i", /* 136*/ + "l2f", /* 137*/ + "l2d", /* 138*/ + "f2i", /* 139*/ + "f2l", /* 140*/ + "f2d", /* 141*/ + "d2i", /* 142*/ + "d2l", /* 143*/ + "d2f", /* 144*/ + "i2b", /* 145*/ + "i2c", /* 146*/ + "i2s", /* 147*/ + "lcmp", /* 148*/ + "fcmpl", /* 149*/ + "fcmpg", /* 150*/ + "dcmpl", /* 151*/ + "dcmpg", /* 152*/ + "ifeq", /* 153*/ + "ifne", /* 154*/ + "iflt", /* 155*/ + "ifge", /* 156*/ + "ifgt", /* 157*/ + "ifle", /* 158*/ + "if_icmpeq", /* 159*/ + "if_icmpne", /* 160*/ + "if_icmplt", /* 161*/ + "if_icmpge", /* 162*/ + "if_icmpgt", /* 163*/ + "if_icmple", /* 164*/ + "if_acmpeq", /* 165*/ + "if_acmpne", /* 166*/ + "goto", /* 167*/ + "jsr", /* 168*/ + "ret", /* 169*/ + "tableswitch", /* 170*/ + "lookupswitch", /* 171*/ + "ireturn", /* 172*/ + "lreturn", /* 173*/ + "freturn", /* 174*/ + "dreturn", /* 175*/ + "areturn", /* 176*/ + "return", /* 177*/ + "getstatic", /* 178*/ + "putstatic", /* 179*/ + "getfield", /* 180*/ + "putfield", /* 181*/ + "invokevirtual", /* 182*/ + "invokespecial", /* 183*/ + "invokestatic", /* 184*/ + "invokeinterface", /* 185*/ + null, + "new", /* 187*/ + "newarray", /* 188*/ + "anewarray", /* 189*/ + "arraylength", /* 190*/ + "athrow", /* 191*/ + "checkcast", /* 192*/ + "instanceof", /* 193*/ + "monitorenter", /* 194*/ + "monitorexit", /* 195*/ + "wide", /* 196*/ + "multianewarray", /* 197*/ + "ifnull", /* 198*/ + "ifnonnull", /* 199*/ + "goto_w", /* 200*/ + "jsr_w" /* 201*/ + }; +} diff --git a/src/main/javassist/bytecode/Opcode.java b/src/main/javassist/bytecode/Opcode.java new file mode 100644 index 0000000..db34b11 --- /dev/null +++ b/src/main/javassist/bytecode/Opcode.java @@ -0,0 +1,447 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.bytecode; + +/** + * JVM Instruction Set. + * + * <p>This interface defines opcodes and + * array types for the NEWARRAY instruction. + * + * @see Mnemonic + */ +public interface Opcode { + /* Opcodes */ + + int AALOAD = 50; + int AASTORE = 83; + int ACONST_NULL = 1; + int ALOAD = 25; + int ALOAD_0 = 42; + int ALOAD_1 = 43; + int ALOAD_2 = 44; + int ALOAD_3 = 45; + int ANEWARRAY = 189; + int ARETURN = 176; + int ARRAYLENGTH = 190; + int ASTORE = 58; + int ASTORE_0 = 75; + int ASTORE_1 = 76; + int ASTORE_2 = 77; + int ASTORE_3 = 78; + int ATHROW = 191; + int BALOAD = 51; + int BASTORE = 84; + int BIPUSH = 16; + int CALOAD = 52; + int CASTORE = 85; + int CHECKCAST = 192; + int D2F = 144; + int D2I = 142; + int D2L = 143; + int DADD = 99; + int DALOAD = 49; + int DASTORE = 82; + int DCMPG = 152; + int DCMPL = 151; + int DCONST_0 = 14; + int DCONST_1 = 15; + int DDIV = 111; + int DLOAD = 24; + int DLOAD_0 = 38; + int DLOAD_1 = 39; + int DLOAD_2 = 40; + int DLOAD_3 = 41; + int DMUL = 107; + int DNEG = 119; + int DREM = 115; + int DRETURN = 175; + int DSTORE = 57; + int DSTORE_0 = 71; + int DSTORE_1 = 72; + int DSTORE_2 = 73; + int DSTORE_3 = 74; + int DSUB = 103; + int DUP = 89; + int DUP2 = 92; + int DUP2_X1 = 93; + int DUP2_X2 = 94; + int DUP_X1 = 90; + int DUP_X2 = 91; + int F2D = 141; + int F2I = 139; + int F2L = 140; + int FADD = 98; + int FALOAD = 48; + int FASTORE = 81; + int FCMPG = 150; + int FCMPL = 149; + int FCONST_0 = 11; + int FCONST_1 = 12; + int FCONST_2 = 13; + int FDIV = 110; + int FLOAD = 23; + int FLOAD_0 = 34; + int FLOAD_1 = 35; + int FLOAD_2 = 36; + int FLOAD_3 = 37; + int FMUL = 106; + int FNEG = 118; + int FREM = 114; + int FRETURN = 174; + int FSTORE = 56; + int FSTORE_0 = 67; + int FSTORE_1 = 68; + int FSTORE_2 = 69; + int FSTORE_3 = 70; + int FSUB = 102; + int GETFIELD = 180; + int GETSTATIC = 178; + int GOTO = 167; + int GOTO_W = 200; + int I2B = 145; + int I2C = 146; + int I2D = 135; + int I2F = 134; + int I2L = 133; + int I2S = 147; + int IADD = 96; + int IALOAD = 46; + int IAND = 126; + int IASTORE = 79; + int ICONST_0 = 3; + int ICONST_1 = 4; + int ICONST_2 = 5; + int ICONST_3 = 6; + int ICONST_4 = 7; + int ICONST_5 = 8; + int ICONST_M1 = 2; + int IDIV = 108; + int IFEQ = 153; + int IFGE = 156; + int IFGT = 157; + int IFLE = 158; + int IFLT = 155; + int IFNE = 154; + int IFNONNULL = 199; + int IFNULL = 198; + int IF_ACMPEQ = 165; + int IF_ACMPNE = 166; + int IF_ICMPEQ = 159; + int IF_ICMPGE = 162; + int IF_ICMPGT = 163; + int IF_ICMPLE = 164; + int IF_ICMPLT = 161; + int IF_ICMPNE = 160; + int IINC = 132; + int ILOAD = 21; + int ILOAD_0 = 26; + int ILOAD_1 = 27; + int ILOAD_2 = 28; + int ILOAD_3 = 29; + int IMUL = 104; + int INEG = 116; + int INSTANCEOF = 193; + int INVOKEINTERFACE = 185; + int INVOKESPECIAL = 183; + int INVOKESTATIC = 184; + int INVOKEVIRTUAL = 182; + int IOR = 128; + int IREM = 112; + int IRETURN = 172; + int ISHL = 120; + int ISHR = 122; + int ISTORE = 54; + int ISTORE_0 = 59; + int ISTORE_1 = 60; + int ISTORE_2 = 61; + int ISTORE_3 = 62; + int ISUB = 100; + int IUSHR = 124; + int IXOR = 130; + int JSR = 168; + int JSR_W = 201; + int L2D = 138; + int L2F = 137; + int L2I = 136; + int LADD = 97; + int LALOAD = 47; + int LAND = 127; + int LASTORE = 80; + int LCMP = 148; + int LCONST_0 = 9; + int LCONST_1 = 10; + int LDC = 18; + int LDC2_W = 20; + int LDC_W = 19; + int LDIV = 109; + int LLOAD = 22; + int LLOAD_0 = 30; + int LLOAD_1 = 31; + int LLOAD_2 = 32; + int LLOAD_3 = 33; + int LMUL = 105; + int LNEG = 117; + int LOOKUPSWITCH = 171; + int LOR = 129; + int LREM = 113; + int LRETURN = 173; + int LSHL = 121; + int LSHR = 123; + int LSTORE = 55; + int LSTORE_0 = 63; + int LSTORE_1 = 64; + int LSTORE_2 = 65; + int LSTORE_3 = 66; + int LSUB = 101; + int LUSHR = 125; + int LXOR = 131; + int MONITORENTER = 194; + int MONITOREXIT = 195; + int MULTIANEWARRAY = 197; + int NEW = 187; + int NEWARRAY = 188; + int NOP = 0; + int POP = 87; + int POP2 = 88; + int PUTFIELD = 181; + int PUTSTATIC = 179; + int RET = 169; + int RETURN = 177; + int SALOAD = 53; + int SASTORE = 86; + int SIPUSH = 17; + int SWAP = 95; + int TABLESWITCH = 170; + int WIDE = 196; + + /* array-type code for the newarray instruction */ + + int T_BOOLEAN = 4; + int T_CHAR = 5; + int T_FLOAT = 6; + int T_DOUBLE = 7; + int T_BYTE = 8; + int T_SHORT = 9; + int T_INT = 10; + int T_LONG = 11; + + /* how many values are pushed on the operand stack. */ + int[] STACK_GROW = { + 0, // nop, 0 + 1, // aconst_null, 1 + 1, // iconst_m1, 2 + 1, // iconst_0, 3 + 1, // iconst_1, 4 + 1, // iconst_2, 5 + 1, // iconst_3, 6 + 1, // iconst_4, 7 + 1, // iconst_5, 8 + 2, // lconst_0, 9 + 2, // lconst_1, 10 + 1, // fconst_0, 11 + 1, // fconst_1, 12 + 1, // fconst_2, 13 + 2, // dconst_0, 14 + 2, // dconst_1, 15 + 1, // bipush, 16 + 1, // sipush, 17 + 1, // ldc, 18 + 1, // ldc_w, 19 + 2, // ldc2_w, 20 + 1, // iload, 21 + 2, // lload, 22 + 1, // fload, 23 + 2, // dload, 24 + 1, // aload, 25 + 1, // iload_0, 26 + 1, // iload_1, 27 + 1, // iload_2, 28 + 1, // iload_3, 29 + 2, // lload_0, 30 + 2, // lload_1, 31 + 2, // lload_2, 32 + 2, // lload_3, 33 + 1, // fload_0, 34 + 1, // fload_1, 35 + 1, // fload_2, 36 + 1, // fload_3, 37 + 2, // dload_0, 38 + 2, // dload_1, 39 + 2, // dload_2, 40 + 2, // dload_3, 41 + 1, // aload_0, 42 + 1, // aload_1, 43 + 1, // aload_2, 44 + 1, // aload_3, 45 + -1, // iaload, 46 + 0, // laload, 47 + -1, // faload, 48 + 0, // daload, 49 + -1, // aaload, 50 + -1, // baload, 51 + -1, // caload, 52 + -1, // saload, 53 + -1, // istore, 54 + -2, // lstore, 55 + -1, // fstore, 56 + -2, // dstore, 57 + -1, // astore, 58 + -1, // istore_0, 59 + -1, // istore_1, 60 + -1, // istore_2, 61 + -1, // istore_3, 62 + -2, // lstore_0, 63 + -2, // lstore_1, 64 + -2, // lstore_2, 65 + -2, // lstore_3, 66 + -1, // fstore_0, 67 + -1, // fstore_1, 68 + -1, // fstore_2, 69 + -1, // fstore_3, 70 + -2, // dstore_0, 71 + -2, // dstore_1, 72 + -2, // dstore_2, 73 + -2, // dstore_3, 74 + -1, // astore_0, 75 + -1, // astore_1, 76 + -1, // astore_2, 77 + -1, // astore_3, 78 + -3, // iastore, 79 + -4, // lastore, 80 + -3, // fastore, 81 + -4, // dastore, 82 + -3, // aastore, 83 + -3, // bastore, 84 + -3, // castore, 85 + -3, // sastore, 86 + -1, // pop, 87 + -2, // pop2, 88 + 1, // dup, 89 + 1, // dup_x1, 90 + 1, // dup_x2, 91 + 2, // dup2, 92 + 2, // dup2_x1, 93 + 2, // dup2_x2, 94 + 0, // swap, 95 + -1, // iadd, 96 + -2, // ladd, 97 + -1, // fadd, 98 + -2, // dadd, 99 + -1, // isub, 100 + -2, // lsub, 101 + -1, // fsub, 102 + -2, // dsub, 103 + -1, // imul, 104 + -2, // lmul, 105 + -1, // fmul, 106 + -2, // dmul, 107 + -1, // idiv, 108 + -2, // ldiv, 109 + -1, // fdiv, 110 + -2, // ddiv, 111 + -1, // irem, 112 + -2, // lrem, 113 + -1, // frem, 114 + -2, // drem, 115 + 0, // ineg, 116 + 0, // lneg, 117 + 0, // fneg, 118 + 0, // dneg, 119 + -1, // ishl, 120 + -1, // lshl, 121 + -1, // ishr, 122 + -1, // lshr, 123 + -1, // iushr, 124 + -1, // lushr, 125 + -1, // iand, 126 + -2, // land, 127 + -1, // ior, 128 + -2, // lor, 129 + -1, // ixor, 130 + -2, // lxor, 131 + 0, // iinc, 132 + 1, // i2l, 133 + 0, // i2f, 134 + 1, // i2d, 135 + -1, // l2i, 136 + -1, // l2f, 137 + 0, // l2d, 138 + 0, // f2i, 139 + 1, // f2l, 140 + 1, // f2d, 141 + -1, // d2i, 142 + 0, // d2l, 143 + -1, // d2f, 144 + 0, // i2b, 145 + 0, // i2c, 146 + 0, // i2s, 147 + -3, // lcmp, 148 + -1, // fcmpl, 149 + -1, // fcmpg, 150 + -3, // dcmpl, 151 + -3, // dcmpg, 152 + -1, // ifeq, 153 + -1, // ifne, 154 + -1, // iflt, 155 + -1, // ifge, 156 + -1, // ifgt, 157 + -1, // ifle, 158 + -2, // if_icmpeq, 159 + -2, // if_icmpne, 160 + -2, // if_icmplt, 161 + -2, // if_icmpge, 162 + -2, // if_icmpgt, 163 + -2, // if_icmple, 164 + -2, // if_acmpeq, 165 + -2, // if_acmpne, 166 + 0, // goto, 167 + 1, // jsr, 168 + 0, // ret, 169 + -1, // tableswitch, 170 + -1, // lookupswitch, 171 + -1, // ireturn, 172 + -2, // lreturn, 173 + -1, // freturn, 174 + -2, // dreturn, 175 + -1, // areturn, 176 + 0, // return, 177 + 0, // getstatic, 178 depends on the type + 0, // putstatic, 179 depends on the type + 0, // getfield, 180 depends on the type + 0, // putfield, 181 depends on the type + 0, // invokevirtual, 182 depends on the type + 0, // invokespecial, 183 depends on the type + 0, // invokestatic, 184 depends on the type + 0, // invokeinterface, 185 depends on the type + 0, // undefined, 186 + 1, // new, 187 + 0, // newarray, 188 + 0, // anewarray, 189 + 0, // arraylength, 190 + -1, // athrow, 191 stack is cleared + 0, // checkcast, 192 + 0, // instanceof, 193 + -1, // monitorenter, 194 + -1, // monitorexit, 195 + 0, // wide, 196 depends on the following opcode + 0, // multianewarray, 197 depends on the dimensions + -1, // ifnull, 198 + -1, // ifnonnull, 199 + 0, // goto_w, 200 + 1 // jsr_w, 201 + }; +} diff --git a/src/main/javassist/bytecode/ParameterAnnotationsAttribute.java b/src/main/javassist/bytecode/ParameterAnnotationsAttribute.java new file mode 100644 index 0000000..246afc1 --- /dev/null +++ b/src/main/javassist/bytecode/ParameterAnnotationsAttribute.java @@ -0,0 +1,214 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.bytecode; + +import java.util.HashMap; +import java.util.Map; +import java.io.IOException; +import java.io.DataInputStream; +import java.io.ByteArrayOutputStream; + +import javassist.bytecode.AnnotationsAttribute.Copier; +import javassist.bytecode.AnnotationsAttribute.Parser; +import javassist.bytecode.AnnotationsAttribute.Renamer; +import javassist.bytecode.annotation.*; + +/** + * A class representing <code>RuntimeVisibleAnnotations_attribute</code> and + * <code>RuntimeInvisibleAnnotations_attribute</code>. + * + * <p>To obtain an ParameterAnnotationAttribute object, invoke + * <code>getAttribute(ParameterAnnotationsAttribute.invisibleTag)</code> + * in <code>MethodInfo</code>. + * The obtained attribute is a + * runtime invisible annotations attribute. + * If the parameter is + * <code>ParameterAnnotationAttribute.visibleTag</code>, then the obtained + * attribute is a runtime visible one. + */ +public class ParameterAnnotationsAttribute extends AttributeInfo { + /** + * The name of the <code>RuntimeVisibleParameterAnnotations</code> + * attribute. + */ + public static final String visibleTag + = "RuntimeVisibleParameterAnnotations"; + + /** + * The name of the <code>RuntimeInvisibleParameterAnnotations</code> + * attribute. + */ + public static final String invisibleTag + = "RuntimeInvisibleParameterAnnotations"; + /** + * Constructs + * a <code>Runtime(In)VisibleParameterAnnotations_attribute</code>. + * + * @param cp constant pool + * @param attrname attribute name (<code>visibleTag</code> or + * <code>invisibleTag</code>). + * @param info the contents of this attribute. It does not + * include <code>attribute_name_index</code> or + * <code>attribute_length</code>. + */ + public ParameterAnnotationsAttribute(ConstPool cp, String attrname, + byte[] info) { + super(cp, attrname, info); + } + + /** + * Constructs an empty + * <code>Runtime(In)VisibleParameterAnnotations_attribute</code>. + * A new annotation can be later added to the created attribute + * by <code>setAnnotations()</code>. + * + * @param cp constant pool + * @param attrname attribute name (<code>visibleTag</code> or + * <code>invisibleTag</code>). + * @see #setAnnotations(Annotation[][]) + */ + public ParameterAnnotationsAttribute(ConstPool cp, String attrname) { + this(cp, attrname, new byte[] { 0 }); + } + + /** + * @param n the attribute name. + */ + ParameterAnnotationsAttribute(ConstPool cp, int n, DataInputStream in) + throws IOException + { + super(cp, n, in); + } + + /** + * Returns <code>num_parameters</code>. + */ + public int numParameters() { + return info[0] & 0xff; + } + + /** + * Copies this attribute and returns a new copy. + */ + public AttributeInfo copy(ConstPool newCp, Map classnames) { + Copier copier = new Copier(info, constPool, newCp, classnames); + try { + copier.parameters(); + return new ParameterAnnotationsAttribute(newCp, getName(), + copier.close()); + } + catch (Exception e) { + throw new RuntimeException(e.toString()); + } + } + + /** + * Parses the annotations and returns a data structure representing + * that parsed annotations. Note that changes of the node values of the + * returned tree are not reflected on the annotations represented by + * this object unless the tree is copied back to this object by + * <code>setAnnotations()</code>. + * + * @return Each element of the returned array represents an array of + * annotations that are associated with each method parameter. + * + * @see #setAnnotations(Annotation[][]) + */ + public Annotation[][] getAnnotations() { + try { + return new Parser(info, constPool).parseParameters(); + } + catch (Exception e) { + throw new RuntimeException(e.toString()); + } + } + + /** + * Changes the annotations represented by this object according to + * the given array of <code>Annotation</code> objects. + * + * @param params the data structure representing the + * new annotations. Every element of this array + * is an array of <code>Annotation</code> and + * it represens annotations of each method parameter. + */ + public void setAnnotations(Annotation[][] params) { + ByteArrayOutputStream output = new ByteArrayOutputStream(); + AnnotationsWriter writer = new AnnotationsWriter(output, constPool); + try { + int n = params.length; + writer.numParameters(n); + for (int i = 0; i < n; ++i) { + Annotation[] anno = params[i]; + writer.numAnnotations(anno.length); + for (int j = 0; j < anno.length; ++j) + anno[j].write(writer); + } + + writer.close(); + } + catch (IOException e) { + throw new RuntimeException(e); // should never reach here. + } + + set(output.toByteArray()); + } + + /** + * @param oldname a JVM class name. + * @param newname a JVM class name. + */ + void renameClass(String oldname, String newname) { + HashMap map = new HashMap(); + map.put(oldname, newname); + renameClass(map); + } + + void renameClass(Map classnames) { + Renamer renamer = new Renamer(info, getConstPool(), classnames); + try { + renamer.parameters(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + void getRefClasses(Map classnames) { renameClass(classnames); } + + /** + * Returns a string representation of this object. + */ + public String toString() { + Annotation[][] aa = getAnnotations(); + StringBuilder sbuf = new StringBuilder(); + int k = 0; + while (k < aa.length) { + Annotation[] a = aa[k++]; + int i = 0; + while (i < a.length) { + sbuf.append(a[i++].toString()); + if (i != a.length) + sbuf.append(" "); + } + + if (k != aa.length) + sbuf.append(", "); + } + + return sbuf.toString(); + + } +} diff --git a/src/main/javassist/bytecode/SignatureAttribute.java b/src/main/javassist/bytecode/SignatureAttribute.java new file mode 100644 index 0000000..958e93f --- /dev/null +++ b/src/main/javassist/bytecode/SignatureAttribute.java @@ -0,0 +1,828 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.bytecode; + +import java.io.DataInputStream; +import java.io.IOException; +import java.util.Map; +import java.util.ArrayList; +import javassist.CtClass; + +/** + * <code>Signature_attribute</code>. + */ +public class SignatureAttribute extends AttributeInfo { + /** + * The name of this attribute <code>"Signature"</code>. + */ + public static final String tag = "Signature"; + + SignatureAttribute(ConstPool cp, int n, DataInputStream in) + throws IOException + { + super(cp, n, in); + } + + /** + * Constructs a Signature attribute. + * + * @param cp a constant pool table. + * @param signature the signature represented by this attribute. + */ + public SignatureAttribute(ConstPool cp, String signature) { + super(cp, tag); + int index = cp.addUtf8Info(signature); + byte[] bvalue = new byte[2]; + bvalue[0] = (byte)(index >>> 8); + bvalue[1] = (byte)index; + set(bvalue); + } + + /** + * Returns the signature indicated by <code>signature_index</code>. + * + * @see #toClassSignature(String) + * @see #toMethodSignature(String) + */ + public String getSignature() { + return getConstPool().getUtf8Info(ByteArray.readU16bit(get(), 0)); + } + + /** + * Sets <code>signature_index</code> to the index of the given signature, + * which is added to a constant pool. + * + * @param sig new signature. + * @since 3.11 + */ + public void setSignature(String sig) { + int index = getConstPool().addUtf8Info(sig); + ByteArray.write16bit(index, info, 0); + } + + /** + * Makes a copy. Class names are replaced according to the + * given <code>Map</code> object. + * + * @param newCp the constant pool table used by the new copy. + * @param classnames pairs of replaced and substituted + * class names. + */ + public AttributeInfo copy(ConstPool newCp, Map classnames) { + return new SignatureAttribute(newCp, getSignature()); + } + + void renameClass(String oldname, String newname) { + String sig = renameClass(getSignature(), oldname, newname); + setSignature(sig); + } + + void renameClass(Map classnames) { + String sig = renameClass(getSignature(), classnames); + setSignature(sig); + } + + static String renameClass(String desc, String oldname, String newname) { + Map map = new java.util.HashMap(); + map.put(oldname, newname); + return renameClass(desc, map); + } + + static String renameClass(String desc, Map map) { + if (map == null) + return desc; + + StringBuilder newdesc = new StringBuilder(); + int head = 0; + int i = 0; + for (;;) { + int j = desc.indexOf('L', i); + if (j < 0) + break; + + StringBuilder nameBuf = new StringBuilder(); + int k = j; + char c; + try { + while ((c = desc.charAt(++k)) != ';') { + nameBuf.append(c); + if (c == '<') { + while ((c = desc.charAt(++k)) != '>') + nameBuf.append(c); + + nameBuf.append(c); + } + } + } + catch (IndexOutOfBoundsException e) { break; } + i = k + 1; + String name = nameBuf.toString(); + String name2 = (String)map.get(name); + if (name2 != null) { + newdesc.append(desc.substring(head, j)); + newdesc.append('L'); + newdesc.append(name2); + newdesc.append(c); + head = i; + } + } + + if (head == 0) + return desc; + else { + int len = desc.length(); + if (head < len) + newdesc.append(desc.substring(head, len)); + + return newdesc.toString(); + } + } + + private static boolean isNamePart(int c) { + return c != ';' && c != '<'; + } + + static private class Cursor { + int position = 0; + + int indexOf(String s, int ch) throws BadBytecode { + int i = s.indexOf(ch, position); + if (i < 0) + throw error(s); + else { + position = i + 1; + return i; + } + } + } + + /** + * Class signature. + */ + public static class ClassSignature { + TypeParameter[] params; + ClassType superClass; + ClassType[] interfaces; + ClassSignature(TypeParameter[] p, ClassType s, ClassType[] i) { + params = p; + superClass = s; + interfaces = i; + } + + /** + * Returns the type parameters. + * + * @return a zero-length array if the type parameters are not specified. + */ + public TypeParameter[] getParameters() { + return params; + } + + /** + * Returns the super class. + */ + public ClassType getSuperClass() { return superClass; } + + /** + * Returns the super interfaces. + * + * @return a zero-length array if the super interfaces are not specified. + */ + public ClassType[] getInterfaces() { return interfaces; } + + /** + * Returns the string representation. + */ + public String toString() { + StringBuffer sbuf = new StringBuffer(); + + TypeParameter.toString(sbuf, params); + sbuf.append(" extends ").append(superClass); + if (interfaces.length > 0) { + sbuf.append(" implements "); + Type.toString(sbuf, interfaces); + } + + return sbuf.toString(); + } + } + + /** + * Method type signature. + */ + public static class MethodSignature { + TypeParameter[] typeParams; + Type[] params; + Type retType; + ObjectType[] exceptions; + + MethodSignature(TypeParameter[] tp, Type[] p, Type ret, ObjectType[] ex) { + typeParams = tp; + params = p; + retType = ret; + exceptions = ex; + } + + /** + * Returns the formal type parameters. + * + * @return a zero-length array if the type parameters are not specified. + */ + public TypeParameter[] getTypeParameters() { return typeParams; } + + /** + * Returns the types of the formal parameters. + * + * @return a zero-length array if no formal parameter is taken. + */ + public Type[] getParameterTypes() { return params; } + + /** + * Returns the type of the returned value. + */ + public Type getReturnType() { return retType; } + + /** + * Returns the types of the exceptions that may be thrown. + * + * @return a zero-length array if exceptions are never thrown or + * the exception types are not parameterized types or type variables. + */ + public ObjectType[] getExceptionTypes() { return exceptions; } + + /** + * Returns the string representation. + */ + public String toString() { + StringBuffer sbuf = new StringBuffer(); + + TypeParameter.toString(sbuf, typeParams); + sbuf.append(" ("); + Type.toString(sbuf, params); + sbuf.append(") "); + sbuf.append(retType); + if (exceptions.length > 0) { + sbuf.append(" throws "); + Type.toString(sbuf, exceptions); + } + + return sbuf.toString(); + } + } + + /** + * Formal type parameters. + */ + public static class TypeParameter { + String name; + ObjectType superClass; + ObjectType[] superInterfaces; + + TypeParameter(String sig, int nb, int ne, ObjectType sc, ObjectType[] si) { + name = sig.substring(nb, ne); + superClass = sc; + superInterfaces = si; + } + + /** + * Returns the name of the type parameter. + */ + public String getName() { + return name; + } + + /** + * Returns the class bound of this parameter. + * + * @return null if the class bound is not specified. + */ + public ObjectType getClassBound() { return superClass; } + + /** + * Returns the interface bound of this parameter. + * + * @return a zero-length array if the interface bound is not specified. + */ + public ObjectType[] getInterfaceBound() { return superInterfaces; } + + /** + * Returns the string representation. + */ + public String toString() { + StringBuffer sbuf = new StringBuffer(getName()); + if (superClass != null) + sbuf.append(" extends ").append(superClass.toString()); + + int len = superInterfaces.length; + if (len > 0) { + for (int i = 0; i < len; i++) { + if (i > 0 || superClass != null) + sbuf.append(" & "); + else + sbuf.append(" extends "); + + sbuf.append(superInterfaces[i].toString()); + } + } + + return sbuf.toString(); + } + + static void toString(StringBuffer sbuf, TypeParameter[] tp) { + sbuf.append('<'); + for (int i = 0; i < tp.length; i++) { + if (i > 0) + sbuf.append(", "); + + sbuf.append(tp[i]); + } + + sbuf.append('>'); + } + } + + /** + * Type argument. + */ + public static class TypeArgument { + ObjectType arg; + char wildcard; + + TypeArgument(ObjectType a, char w) { + arg = a; + wildcard = w; + } + + /** + * Returns the kind of this type argument. + * + * @return <code>' '</code> (not-wildcard), <code>'*'</code> (wildcard), <code>'+'</code> (wildcard with + * upper bound), or <code>'-'</code> (wildcard with lower bound). + */ + public char getKind() { return wildcard; } + + /** + * Returns true if this type argument is a wildcard type + * such as <code>?</code>, <code>? extends String</code>, or <code>? super Integer</code>. + */ + public boolean isWildcard() { return wildcard != ' '; } + + /** + * Returns the type represented by this argument + * if the argument is not a wildcard type. Otherwise, this method + * returns the upper bound (if the kind is '+'), + * the lower bound (if the kind is '-'), or null (if the upper or lower + * bound is not specified). + */ + public ObjectType getType() { return arg; } + + /** + * Returns the string representation. + */ + public String toString() { + if (wildcard == '*') + return "?"; + + String type = arg.toString(); + if (wildcard == ' ') + return type; + else if (wildcard == '+') + return "? extends " + type; + else + return "? super " + type; + } + } + + /** + * Primitive types and object types. + */ + public static abstract class Type { + static void toString(StringBuffer sbuf, Type[] ts) { + for (int i = 0; i < ts.length; i++) { + if (i > 0) + sbuf.append(", "); + + sbuf.append(ts[i]); + } + } + } + + /** + * Primitive types. + */ + public static class BaseType extends Type { + char descriptor; + BaseType(char c) { descriptor = c; } + + /** + * Returns the descriptor representing this primitive type. + * + * @see javassist.bytecode.Descriptor + */ + public char getDescriptor() { return descriptor; } + + /** + * Returns the <code>CtClass</code> representing this + * primitive type. + */ + public CtClass getCtlass() { + return Descriptor.toPrimitiveClass(descriptor); + } + + /** + * Returns the string representation. + */ + public String toString() { + return Descriptor.toClassName(Character.toString(descriptor)); + } + } + + /** + * Class types, array types, and type variables. + */ + public static abstract class ObjectType extends Type {} + + /** + * Class types. + */ + public static class ClassType extends ObjectType { + String name; + TypeArgument[] arguments; + + static ClassType make(String s, int b, int e, + TypeArgument[] targs, ClassType parent) { + if (parent == null) + return new ClassType(s, b, e, targs); + else + return new NestedClassType(s, b, e, targs, parent); + } + + ClassType(String signature, int begin, int end, TypeArgument[] targs) { + name = signature.substring(begin, end).replace('/', '.'); + arguments = targs; + } + + /** + * Returns the class name. + */ + public String getName() { + return name; + } + + /** + * Returns the type arguments. + * + * @return null if no type arguments are given to this class. + */ + public TypeArgument[] getTypeArguments() { return arguments; } + + /** + * If this class is a member of another class, returns the + * class in which this class is declared. + * + * @return null if this class is not a member of another class. + */ + public ClassType getDeclaringClass() { return null; } + + /** + * Returns the string representation. + */ + public String toString() { + StringBuffer sbuf = new StringBuffer(); + ClassType parent = getDeclaringClass(); + if (parent != null) + sbuf.append(parent.toString()).append('.'); + + sbuf.append(name); + if (arguments != null) { + sbuf.append('<'); + int n = arguments.length; + for (int i = 0; i < n; i++) { + if (i > 0) + sbuf.append(", "); + + sbuf.append(arguments[i].toString()); + } + + sbuf.append('>'); + } + + return sbuf.toString(); + } + } + + /** + * Nested class types. + */ + public static class NestedClassType extends ClassType { + ClassType parent; + NestedClassType(String s, int b, int e, + TypeArgument[] targs, ClassType p) { + super(s, b, e, targs); + parent = p; + } + + /** + * Returns the class that declares this nested class. + * This nested class is a member of that declaring class. + */ + public ClassType getDeclaringClass() { return parent; } + } + + /** + * Array types. + */ + public static class ArrayType extends ObjectType { + int dim; + Type componentType; + + public ArrayType(int d, Type comp) { + dim = d; + componentType = comp; + } + + /** + * Returns the dimension of the array. + */ + public int getDimension() { return dim; } + + /** + * Returns the component type. + */ + public Type getComponentType() { + return componentType; + } + + /** + * Returns the string representation. + */ + public String toString() { + StringBuffer sbuf = new StringBuffer(componentType.toString()); + for (int i = 0; i < dim; i++) + sbuf.append("[]"); + + return sbuf.toString(); + } + } + + /** + * Type variables. + */ + public static class TypeVariable extends ObjectType { + String name; + + TypeVariable(String sig, int begin, int end) { + name = sig.substring(begin, end); + } + + /** + * Returns the variable name. + */ + public String getName() { + return name; + } + + /** + * Returns the string representation. + */ + public String toString() { + return name; + } + } + + /** + * Parses the given signature string as a class signature. + * + * @param sig the signature. + * @throws BadBytecode thrown when a syntactical error is found. + * @since 3.5 + */ + public static ClassSignature toClassSignature(String sig) throws BadBytecode { + try { + return parseSig(sig); + } + catch (IndexOutOfBoundsException e) { + throw error(sig); + } + } + + /** + * Parses the given signature string as a method type signature. + * + * @param sig the signature. + * @throws BadBytecode thrown when a syntactical error is found. + * @since 3.5 + */ + public static MethodSignature toMethodSignature(String sig) throws BadBytecode { + try { + return parseMethodSig(sig); + } + catch (IndexOutOfBoundsException e) { + throw error(sig); + } + } + + /** + * Parses the given signature string as a field type signature. + * + * @param sig the signature string. + * @return the field type signature. + * @throws BadBytecode thrown when a syntactical error is found. + * @since 3.5 + */ + public static ObjectType toFieldSignature(String sig) throws BadBytecode { + try { + return parseObjectType(sig, new Cursor(), false); + } + catch (IndexOutOfBoundsException e) { + throw error(sig); + } + } + + private static ClassSignature parseSig(String sig) + throws BadBytecode, IndexOutOfBoundsException + { + Cursor cur = new Cursor(); + TypeParameter[] tp = parseTypeParams(sig, cur); + ClassType superClass = parseClassType(sig, cur); + int sigLen = sig.length(); + ArrayList ifArray = new ArrayList(); + while (cur.position < sigLen && sig.charAt(cur.position) == 'L') + ifArray.add(parseClassType(sig, cur)); + + ClassType[] ifs + = (ClassType[])ifArray.toArray(new ClassType[ifArray.size()]); + return new ClassSignature(tp, superClass, ifs); + } + + private static MethodSignature parseMethodSig(String sig) + throws BadBytecode + { + Cursor cur = new Cursor(); + TypeParameter[] tp = parseTypeParams(sig, cur); + if (sig.charAt(cur.position++) != '(') + throw error(sig); + + ArrayList params = new ArrayList(); + while (sig.charAt(cur.position) != ')') { + Type t = parseType(sig, cur); + params.add(t); + } + + cur.position++; + Type ret = parseType(sig, cur); + int sigLen = sig.length(); + ArrayList exceptions = new ArrayList(); + while (cur.position < sigLen && sig.charAt(cur.position) == '^') { + cur.position++; + ObjectType t = parseObjectType(sig, cur, false); + if (t instanceof ArrayType) + throw error(sig); + + exceptions.add(t); + } + + Type[] p = (Type[])params.toArray(new Type[params.size()]); + ObjectType[] ex = (ObjectType[])exceptions.toArray(new ObjectType[exceptions.size()]); + return new MethodSignature(tp, p, ret, ex); + } + + private static TypeParameter[] parseTypeParams(String sig, Cursor cur) + throws BadBytecode + { + ArrayList typeParam = new ArrayList(); + if (sig.charAt(cur.position) == '<') { + cur.position++; + while (sig.charAt(cur.position) != '>') { + int nameBegin = cur.position; + int nameEnd = cur.indexOf(sig, ':'); + ObjectType classBound = parseObjectType(sig, cur, true); + ArrayList ifBound = new ArrayList(); + while (sig.charAt(cur.position) == ':') { + cur.position++; + ObjectType t = parseObjectType(sig, cur, false); + ifBound.add(t); + } + + TypeParameter p = new TypeParameter(sig, nameBegin, nameEnd, + classBound, (ObjectType[])ifBound.toArray(new ObjectType[ifBound.size()])); + typeParam.add(p); + } + + cur.position++; + } + + return (TypeParameter[])typeParam.toArray(new TypeParameter[typeParam.size()]); + } + + private static ObjectType parseObjectType(String sig, Cursor c, boolean dontThrow) + throws BadBytecode + { + int i; + int begin = c.position; + switch (sig.charAt(begin)) { + case 'L' : + return parseClassType2(sig, c, null); + case 'T' : + i = c.indexOf(sig, ';'); + return new TypeVariable(sig, begin + 1, i); + case '[' : + return parseArray(sig, c); + default : + if (dontThrow) + return null; + else + throw error(sig); + } + } + + private static ClassType parseClassType(String sig, Cursor c) + throws BadBytecode + { + if (sig.charAt(c.position) == 'L') + return parseClassType2(sig, c, null); + else + throw error(sig); + } + + private static ClassType parseClassType2(String sig, Cursor c, ClassType parent) + throws BadBytecode + { + int start = ++c.position; + char t; + do { + t = sig.charAt(c.position++); + } while (t != '$' && t != '<' && t != ';'); + int end = c.position - 1; + TypeArgument[] targs; + if (t == '<') { + targs = parseTypeArgs(sig, c); + t = sig.charAt(c.position++); + } + else + targs = null; + + ClassType thisClass = ClassType.make(sig, start, end, targs, parent); + if (t == '$') { + c.position--; + return parseClassType2(sig, c, thisClass); + } + else + return thisClass; + } + + private static TypeArgument[] parseTypeArgs(String sig, Cursor c) throws BadBytecode { + ArrayList args = new ArrayList(); + char t; + while ((t = sig.charAt(c.position++)) != '>') { + TypeArgument ta; + if (t == '*' ) + ta = new TypeArgument(null, '*'); + else { + if (t != '+' && t != '-') { + t = ' '; + c.position--; + } + + ta = new TypeArgument(parseObjectType(sig, c, false), t); + } + + args.add(ta); + } + + return (TypeArgument[])args.toArray(new TypeArgument[args.size()]); + } + + private static ObjectType parseArray(String sig, Cursor c) throws BadBytecode { + int dim = 1; + while (sig.charAt(++c.position) == '[') + dim++; + + return new ArrayType(dim, parseType(sig, c)); + } + + private static Type parseType(String sig, Cursor c) throws BadBytecode { + Type t = parseObjectType(sig, c, true); + if (t == null) + t = new BaseType(sig.charAt(c.position++)); + + return t; + } + + private static BadBytecode error(String sig) { + return new BadBytecode("bad signature: " + sig); + } +} diff --git a/src/main/javassist/bytecode/SourceFileAttribute.java b/src/main/javassist/bytecode/SourceFileAttribute.java new file mode 100644 index 0000000..c104eb7 --- /dev/null +++ b/src/main/javassist/bytecode/SourceFileAttribute.java @@ -0,0 +1,70 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.bytecode; + +import java.io.DataInputStream; +import java.io.IOException; +import java.util.Map; + +/** + * <code>SourceFile_attribute</code>. + */ +public class SourceFileAttribute extends AttributeInfo { + /** + * The name of this attribute <code>"SourceFile"</code>. + */ + public static final String tag = "SourceFile"; + + SourceFileAttribute(ConstPool cp, int n, DataInputStream in) + throws IOException + { + super(cp, n, in); + } + + /** + * Constructs a SourceFile attribute. + * + * @param cp a constant pool table. + * @param filename the name of the source file. + */ + public SourceFileAttribute(ConstPool cp, String filename) { + super(cp, tag); + int index = cp.addUtf8Info(filename); + byte[] bvalue = new byte[2]; + bvalue[0] = (byte)(index >>> 8); + bvalue[1] = (byte)index; + set(bvalue); + } + + /** + * Returns the file name indicated by <code>sourcefile_index</code>. + */ + public String getFileName() { + return getConstPool().getUtf8Info(ByteArray.readU16bit(get(), 0)); + } + + /** + * Makes a copy. Class names are replaced according to the + * given <code>Map</code> object. + * + * @param newCp the constant pool table used by the new copy. + * @param classnames pairs of replaced and substituted + * class names. + */ + public AttributeInfo copy(ConstPool newCp, Map classnames) { + return new SourceFileAttribute(newCp, getFileName()); + } +} diff --git a/src/main/javassist/bytecode/StackMap.java b/src/main/javassist/bytecode/StackMap.java new file mode 100644 index 0000000..ac0582e --- /dev/null +++ b/src/main/javassist/bytecode/StackMap.java @@ -0,0 +1,544 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.bytecode; + +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.IOException; +import java.util.Map; + +import javassist.CannotCompileException; +import javassist.bytecode.StackMapTable.InsertLocal; +import javassist.bytecode.StackMapTable.NewRemover; +import javassist.bytecode.StackMapTable.Shifter; + +/** + * Another <code>stack_map</code> attribute defined in CLDC 1.1 for J2ME. + * + * <p>This is an entry in the attributes table of a Code attribute. + * It was introduced by J2ME CLDC 1.1 (JSR 139) for pre-verification. + * + * <p>According to the CLDC specification, the sizes of some fields are not 16bit + * but 32bit if the code size is more than 64K or the number of the local variables + * is more than 64K. However, for the J2ME CLDC technology, they are always 16bit. + * The implementation of the StackMap class assumes they are 16bit. + * + * @see MethodInfo#doPreverify + * @see StackMapTable + * @since 3.12 + */ +public class StackMap extends AttributeInfo { + /** + * The name of this attribute <code>"StackMap"</code>. + */ + public static final String tag = "StackMap"; + + + /** + * Constructs a <code>stack_map</code> attribute. + */ + StackMap(ConstPool cp, byte[] newInfo) { + super(cp, tag, newInfo); + } + + StackMap(ConstPool cp, int name_id, DataInputStream in) + throws IOException + { + super(cp, name_id, in); + } + + /** + * Returns <code>number_of_entries</code>. + */ + public int numOfEntries() { + return ByteArray.readU16bit(info, 0); + } + + /** + * <code>Top_variable_info.tag</code>. + */ + public static final int TOP = 0; + + /** + * <code>Integer_variable_info.tag</code>. + */ + public static final int INTEGER = 1; + + /** + * <code>Float_variable_info.tag</code>. + */ + public static final int FLOAT = 2; + + /** + * <code>Double_variable_info.tag</code>. + */ + public static final int DOUBLE = 3; + + /** + * <code>Long_variable_info.tag</code>. + */ + public static final int LONG = 4; + + /** + * <code>Null_variable_info.tag</code>. + */ + public static final int NULL = 5; + + /** + * <code>UninitializedThis_variable_info.tag</code>. + */ + public static final int THIS = 6; + + /** + * <code>Object_variable_info.tag</code>. + */ + public static final int OBJECT = 7; + + /** + * <code>Uninitialized_variable_info.tag</code>. + */ + public static final int UNINIT = 8; + + /** + * Makes a copy. + */ + public AttributeInfo copy(ConstPool newCp, Map classnames) { + Copier copier = new Copier(this, newCp, classnames); + copier.visit(); + return copier.getStackMap(); + } + + /** + * A code walker for a StackMap attribute. + */ + public static class Walker { + byte[] info; + + /** + * Constructs a walker. + */ + public Walker(StackMap sm) { + info = sm.get(); + } + + /** + * Visits each entry of the stack map frames. + */ + public void visit() { + int num = ByteArray.readU16bit(info, 0); + int pos = 2; + for (int i = 0; i < num; i++) { + int offset = ByteArray.readU16bit(info, pos); + int numLoc = ByteArray.readU16bit(info, pos + 2); + pos = locals(pos + 4, offset, numLoc); + int numStack = ByteArray.readU16bit(info, pos); + pos = stack(pos + 2, offset, numStack); + } + } + + /** + * Invoked when <code>locals</code> of <code>stack_map_frame</code> + * is visited. + */ + public int locals(int pos, int offset, int num) { + return typeInfoArray(pos, offset, num, true); + } + + /** + * Invoked when <code>stack</code> of <code>stack_map_frame</code> + * is visited. + */ + public int stack(int pos, int offset, int num) { + return typeInfoArray(pos, offset, num, false); + } + + /** + * Invoked when an array of <code>verification_type_info</code> is + * visited. + * + * @param num the number of elements. + * @param isLocals true if this array is for <code>locals</code>. + * false if it is for <code>stack</code>. + */ + public int typeInfoArray(int pos, int offset, int num, boolean isLocals) { + for (int k = 0; k < num; k++) + pos = typeInfoArray2(k, pos); + + return pos; + } + + int typeInfoArray2(int k, int pos) { + byte tag = info[pos]; + if (tag == OBJECT) { + int clazz = ByteArray.readU16bit(info, pos + 1); + objectVariable(pos, clazz); + pos += 3; + } + else if (tag == UNINIT) { + int offsetOfNew = ByteArray.readU16bit(info, pos + 1); + uninitialized(pos, offsetOfNew); + pos += 3; + } + else { + typeInfo(pos, tag); + pos++; + } + + return pos; + } + + /** + * Invoked when an element of <code>verification_type_info</code> + * (except <code>Object_variable_info</code> and + * <code>Uninitialized_variable_info</code>) is visited. + */ + public void typeInfo(int pos, byte tag) {} + + /** + * Invoked when an element of type <code>Object_variable_info</code> + * is visited. + */ + public void objectVariable(int pos, int clazz) {} + + /** + * Invoked when an element of type <code>Uninitialized_variable_info</code> + * is visited. + */ + public void uninitialized(int pos, int offset) {} + } + + static class Copier extends Walker { + byte[] dest; + ConstPool srcCp, destCp; + Map classnames; + + Copier(StackMap map, ConstPool newCp, Map classnames) { + super(map); + srcCp = map.getConstPool(); + dest = new byte[info.length]; + destCp = newCp; + this.classnames = classnames; + } + + public void visit() { + int num = ByteArray.readU16bit(info, 0); + ByteArray.write16bit(num, dest, 0); + super.visit(); + } + + public int locals(int pos, int offset, int num) { + ByteArray.write16bit(offset, dest, pos - 4); + return super.locals(pos, offset, num); + } + + public int typeInfoArray(int pos, int offset, int num, boolean isLocals) { + ByteArray.write16bit(num, dest, pos - 2); + return super.typeInfoArray(pos, offset, num, isLocals); + } + + public void typeInfo(int pos, byte tag) { + dest[pos] = tag; + } + + public void objectVariable(int pos, int clazz) { + dest[pos] = OBJECT; + int newClazz = srcCp.copy(clazz, destCp, classnames); + ByteArray.write16bit(newClazz, dest, pos + 1); + } + + public void uninitialized(int pos, int offset) { + dest[pos] = UNINIT; + ByteArray.write16bit(offset, dest, pos + 1); + } + + public StackMap getStackMap() { + return new StackMap(destCp, dest); + } + } + + /** + * Updates this stack map table when a new local variable is inserted + * for a new parameter. + * + * @param index the index of the added local variable. + * @param tag the type tag of that local variable. + * It is available by <code>StackMapTable.typeTagOf(char)</code>. + * @param classInfo the index of the <code>CONSTANT_Class_info</code> structure + * in a constant pool table. This should be zero unless the tag + * is <code>ITEM_Object</code>. + * + * @see javassist.CtBehavior#addParameter(javassist.CtClass) + * @see StackMapTable#typeTagOf(char) + * @see ConstPool + */ + public void insertLocal(int index, int tag, int classInfo) + throws BadBytecode + { + byte[] data = new InsertLocal(this, index, tag, classInfo).doit(); + this.set(data); + } + + static class SimpleCopy extends Walker { + Writer writer; + + SimpleCopy(StackMap map) { + super(map); + writer = new Writer(); + } + + byte[] doit() { + visit(); + return writer.toByteArray(); + } + + public void visit() { + int num = ByteArray.readU16bit(info, 0); + writer.write16bit(num); + super.visit(); + } + + public int locals(int pos, int offset, int num) { + writer.write16bit(offset); + return super.locals(pos, offset, num); + } + + public int typeInfoArray(int pos, int offset, int num, boolean isLocals) { + writer.write16bit(num); + return super.typeInfoArray(pos, offset, num, isLocals); + } + + public void typeInfo(int pos, byte tag) { + writer.writeVerifyTypeInfo(tag, 0); + } + + public void objectVariable(int pos, int clazz) { + writer.writeVerifyTypeInfo(OBJECT, clazz); + } + + public void uninitialized(int pos, int offset) { + writer.writeVerifyTypeInfo(UNINIT, offset); + } + } + + static class InsertLocal extends SimpleCopy { + private int varIndex; + private int varTag, varData; + + InsertLocal(StackMap map, int varIndex, int varTag, int varData) { + super(map); + this.varIndex = varIndex; + this.varTag = varTag; + this.varData = varData; + } + + public int typeInfoArray(int pos, int offset, int num, boolean isLocals) { + if (!isLocals || num < varIndex) + return super.typeInfoArray(pos, offset, num, isLocals); + + writer.write16bit(num + 1); + for (int k = 0; k < num; k++) { + if (k == varIndex) + writeVarTypeInfo(); + + pos = typeInfoArray2(k, pos); + } + + if (num == varIndex) + writeVarTypeInfo(); + + return pos; + } + + private void writeVarTypeInfo() { + if (varTag == OBJECT) + writer.writeVerifyTypeInfo(OBJECT, varData); + else if (varTag == UNINIT) + writer.writeVerifyTypeInfo(UNINIT, varData); + else + writer.writeVerifyTypeInfo(varTag, 0); + } + } + + void shiftPc(int where, int gapSize, boolean exclusive) + throws BadBytecode + { + new Shifter(this, where, gapSize, exclusive).visit(); + } + + static class Shifter extends Walker { + private int where, gap; + private boolean exclusive; + + public Shifter(StackMap smt, int where, int gap, boolean exclusive) { + super(smt); + this.where = where; + this.gap = gap; + this.exclusive = exclusive; + } + + public int locals(int pos, int offset, int num) { + if (exclusive ? where <= offset : where < offset) + ByteArray.write16bit(offset + gap, info, pos - 4); + + return super.locals(pos, offset, num); + } + } + + /** + * Undocumented method. Do not use; internal-use only. + * + * <p>This method is for javassist.convert.TransformNew. + * It is called to update the stack map when + * the NEW opcode (and the following DUP) is removed. + * + * @param where the position of the removed NEW opcode. + */ + public void removeNew(int where) throws CannotCompileException { + byte[] data = new NewRemover(this, where).doit(); + this.set(data); + } + + static class NewRemover extends SimpleCopy { + int posOfNew; + + NewRemover(StackMap map, int where) { + super(map); + posOfNew = where; + } + + public int stack(int pos, int offset, int num) { + return stackTypeInfoArray(pos, offset, num); + } + + private int stackTypeInfoArray(int pos, int offset, int num) { + int p = pos; + int count = 0; + for (int k = 0; k < num; k++) { + byte tag = info[p]; + if (tag == OBJECT) + p += 3; + else if (tag == UNINIT) { + int offsetOfNew = ByteArray.readU16bit(info, p + 1); + if (offsetOfNew == posOfNew) + count++; + + p += 3; + } + else + p++; + } + + writer.write16bit(num - count); + for (int k = 0; k < num; k++) { + byte tag = info[pos]; + if (tag == OBJECT) { + int clazz = ByteArray.readU16bit(info, pos + 1); + objectVariable(pos, clazz); + pos += 3; + } + else if (tag == UNINIT) { + int offsetOfNew = ByteArray.readU16bit(info, pos + 1); + if (offsetOfNew != posOfNew) + uninitialized(pos, offsetOfNew); + + pos += 3; + } + else { + typeInfo(pos, tag); + pos++; + } + } + + return pos; + } + } + + /** + * Prints this stack map. + */ + public void print(java.io.PrintWriter out) { + new Printer(this, out).print(); + } + + static class Printer extends Walker { + private java.io.PrintWriter writer; + + public Printer(StackMap map, java.io.PrintWriter out) { + super(map); + writer = out; + } + + public void print() { + int num = ByteArray.readU16bit(info, 0); + writer.println(num + " entries"); + visit(); + } + + public int locals(int pos, int offset, int num) { + writer.println(" * offset " + offset); + return super.locals(pos, offset, num); + } + } + + /** + * Internal use only. + */ + public static class Writer { + // see javassist.bytecode.stackmap.MapMaker + + private ByteArrayOutputStream output; + + /** + * Constructs a writer. + */ + public Writer() { + output = new ByteArrayOutputStream(); + } + + /** + * Converts the written data into a byte array. + */ + public byte[] toByteArray() { + return output.toByteArray(); + } + + /** + * Converts to a <code>StackMap</code> attribute. + */ + public StackMap toStackMap(ConstPool cp) { + return new StackMap(cp, output.toByteArray()); + } + + /** + * Writes a <code>union verification_type_info</code> value. + * + * @param data <code>cpool_index</code> or <code>offset</code>. + */ + public void writeVerifyTypeInfo(int tag, int data) { + output.write(tag); + if (tag == StackMap.OBJECT || tag == StackMap.UNINIT) + write16bit(data); + } + + /** + * Writes a 16bit value. + */ + public void write16bit(int value) { + output.write((value >>> 8) & 0xff); + output.write(value & 0xff); + } + } +} diff --git a/src/main/javassist/bytecode/StackMapTable.java b/src/main/javassist/bytecode/StackMapTable.java new file mode 100644 index 0000000..f3cea6f --- /dev/null +++ b/src/main/javassist/bytecode/StackMapTable.java @@ -0,0 +1,949 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.bytecode; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.ByteArrayOutputStream; +import java.io.PrintWriter; +import java.io.IOException; +import java.util.Map; +import javassist.CannotCompileException; + +/** + * <code>stack_map</code> attribute. + * + * <p>This is an entry in the attributes table of a Code attribute. + * It was introduced by J2SE 6 for the process of verification by + * typechecking. + * + * @see StackMap + * @since 3.4 + */ +public class StackMapTable extends AttributeInfo { + /** + * The name of this attribute <code>"StackMapTable"</code>. + */ + public static final String tag = "StackMapTable"; + + /** + * Constructs a <code>stack_map</code> attribute. + */ + StackMapTable(ConstPool cp, byte[] newInfo) { + super(cp, tag, newInfo); + } + + StackMapTable(ConstPool cp, int name_id, DataInputStream in) + throws IOException + { + super(cp, name_id, in); + } + + /** + * Makes a copy. + * + * @exception RuntimeCopyException if a <code>BadBytecode</code> + * exception is thrown while copying, + * it is converted into + * <code>RuntimeCopyException</code>. + * + */ + public AttributeInfo copy(ConstPool newCp, Map classnames) + throws RuntimeCopyException + { + try { + return new StackMapTable(newCp, + new Copier(this.constPool, info, newCp).doit()); + } + catch (BadBytecode e) { + throw new RuntimeCopyException("bad bytecode. fatal?"); + } + } + + /** + * An exception that may be thrown by <code>copy()</code> + * in <code>StackMapTable</code>. + */ + public static class RuntimeCopyException extends RuntimeException { + /** + * Constructs an exception. + */ + public RuntimeCopyException(String s) { + super(s); + } + } + + void write(DataOutputStream out) throws IOException { + super.write(out); + } + + /** + * <code>Top_variable_info.tag</code>. + */ + public static final int TOP = 0; + + /** + * <code>Integer_variable_info.tag</code>. + */ + public static final int INTEGER = 1; + + /** + * <code>Float_variable_info.tag</code>. + */ + public static final int FLOAT = 2; + + /** + * <code>Double_variable_info.tag</code>. + */ + public static final int DOUBLE = 3; + + /** + * <code>Long_variable_info.tag</code>. + */ + public static final int LONG = 4; + + /** + * <code>Null_variable_info.tag</code>. + */ + public static final int NULL = 5; + + /** + * <code>UninitializedThis_variable_info.tag</code>. + */ + public static final int THIS = 6; + + /** + * <code>Object_variable_info.tag</code>. + */ + public static final int OBJECT = 7; + + /** + * <code>Uninitialized_variable_info.tag</code>. + */ + public static final int UNINIT = 8; + + /** + * A code walker for a StackMapTable attribute. + */ + public static class Walker { + byte[] info; + int numOfEntries; + + /** + * Constructs a walker. + * + * @param smt the StackMapTable that this walker + * walks around. + */ + public Walker(StackMapTable smt) { + this(smt.get()); + } + + /** + * Constructs a walker. + * + * @param data the <code>info</code> field of the + * <code>attribute_info</code> structure. + * It can be obtained by <code>get()</code> + * in the <code>AttributeInfo</code> class. + */ + public Walker(byte[] data) { + info = data; + numOfEntries = ByteArray.readU16bit(data, 0); + } + + /** + * Returns the number of the entries. + */ + public final int size() { return numOfEntries; } + + /** + * Visits each entry of the stack map frames. + */ + public void parse() throws BadBytecode { + int n = numOfEntries; + int pos = 2; + for (int i = 0; i < n; i++) + pos = stackMapFrames(pos, i); + } + + /** + * Invoked when the next entry of the stack map frames is visited. + * + * @param pos the position of the frame in the <code>info</code> + * field of <code>attribute_info</code> structure. + * @param nth the frame is the N-th + * (0, 1st, 2nd, 3rd, 4th, ...) entry. + * @return the position of the next frame. + */ + int stackMapFrames(int pos, int nth) throws BadBytecode { + int type = info[pos] & 0xff; + if (type < 64) { + sameFrame(pos, type); + pos++; + } + else if (type < 128) + pos = sameLocals(pos, type); + else if (type < 247) + throw new BadBytecode("bad frame_type in StackMapTable"); + else if (type == 247) // SAME_LOCALS_1_STACK_ITEM_EXTENDED + pos = sameLocals(pos, type); + else if (type < 251) { + int offset = ByteArray.readU16bit(info, pos + 1); + chopFrame(pos, offset, 251 - type); + pos += 3; + } + else if (type == 251) { // SAME_FRAME_EXTENDED + int offset = ByteArray.readU16bit(info, pos + 1); + sameFrame(pos, offset); + pos += 3; + } + else if (type < 255) + pos = appendFrame(pos, type); + else // FULL_FRAME + pos = fullFrame(pos); + + return pos; + } + + /** + * Invoked if the visited frame is a <code>same_frame</code> or + * a <code>same_frame_extended</code>. + * + * @param pos the position of this frame in the <code>info</code> + * field of <code>attribute_info</code> structure. + * @param offsetDelta + */ + public void sameFrame(int pos, int offsetDelta) throws BadBytecode {} + + private int sameLocals(int pos, int type) throws BadBytecode { + int top = pos; + int offset; + if (type < 128) + offset = type - 64; + else { // type == 247 + offset = ByteArray.readU16bit(info, pos + 1); + pos += 2; + } + + int tag = info[pos + 1] & 0xff; + int data = 0; + if (tag == OBJECT || tag == UNINIT) { + data = ByteArray.readU16bit(info, pos + 2); + pos += 2; + } + + sameLocals(top, offset, tag, data); + return pos + 2; + } + + /** + * Invoked if the visited frame is a <code>same_locals_1_stack_item_frame</code> + * or a <code>same_locals_1_stack_item_frame_extended</code>. + * + * @param pos the position. + * @param offsetDelta + * @param stackTag <code>stack[0].tag</code>. + * @param stackData <code>stack[0].cpool_index</code> + * if the tag is <code>OBJECT</code>, + * or <code>stack[0].offset</code> + * if the tag is <code>UNINIT</code>. + */ + public void sameLocals(int pos, int offsetDelta, int stackTag, int stackData) + throws BadBytecode {} + + /** + * Invoked if the visited frame is a <code>chop_frame</code>. + * + * @param pos the position. + * @param offsetDelta + * @param k the <cod>k</code> last locals are absent. + */ + public void chopFrame(int pos, int offsetDelta, int k) throws BadBytecode {} + + private int appendFrame(int pos, int type) throws BadBytecode { + int k = type - 251; + int offset = ByteArray.readU16bit(info, pos + 1); + int[] tags = new int[k]; + int[] data = new int[k]; + int p = pos + 3; + for (int i = 0; i < k; i++) { + int tag = info[p] & 0xff; + tags[i] = tag; + if (tag == OBJECT || tag == UNINIT) { + data[i] = ByteArray.readU16bit(info, p + 1); + p += 3; + } + else { + data[i] = 0; + p++; + } + } + + appendFrame(pos, offset, tags, data); + return p; + } + + /** + * Invoked if the visited frame is a <code>append_frame</code>. + * + * @param pos the position. + * @param offsetDelta + * @param tags <code>locals[i].tag</code>. + * @param data <code>locals[i].cpool_index</code> + * or <cod>locals[i].offset</code>. + */ + public void appendFrame(int pos, int offsetDelta, int[] tags, int[] data) + throws BadBytecode {} + + private int fullFrame(int pos) throws BadBytecode { + int offset = ByteArray.readU16bit(info, pos + 1); + int numOfLocals = ByteArray.readU16bit(info, pos + 3); + int[] localsTags = new int[numOfLocals]; + int[] localsData = new int[numOfLocals]; + int p = verifyTypeInfo(pos + 5, numOfLocals, localsTags, localsData); + int numOfItems = ByteArray.readU16bit(info, p); + int[] itemsTags = new int[numOfItems]; + int[] itemsData = new int[numOfItems]; + p = verifyTypeInfo(p + 2, numOfItems, itemsTags, itemsData); + fullFrame(pos, offset, localsTags, localsData, itemsTags, itemsData); + return p; + } + + /** + * Invoked if the visited frame is <code>full_frame</code>. + * + * @param pos the position. + * @param offsetDelta + * @param localTags <code>locals[i].tag</code> + * @param localData <code>locals[i].cpool_index</code> + * or <code>locals[i].offset</code> + * @param stackTags <code>stack[i].tag</code> + * @param stackData <code>stack[i].cpool_index</code> + * or <code>stack[i].offset</code> + */ + public void fullFrame(int pos, int offsetDelta, int[] localTags, int[] localData, + int[] stackTags, int[] stackData) + throws BadBytecode {} + + private int verifyTypeInfo(int pos, int n, int[] tags, int[] data) { + for (int i = 0; i < n; i++) { + int tag = info[pos++] & 0xff; + tags[i] = tag; + if (tag == OBJECT || tag == UNINIT) { + data[i] = ByteArray.readU16bit(info, pos); + pos += 2; + } + } + + return pos; + } + } + + static class SimpleCopy extends Walker { + private Writer writer; + + public SimpleCopy(byte[] data) { + super(data); + writer = new Writer(data.length); + } + + public byte[] doit() throws BadBytecode { + parse(); + return writer.toByteArray(); + } + + public void sameFrame(int pos, int offsetDelta) { + writer.sameFrame(offsetDelta); + } + + public void sameLocals(int pos, int offsetDelta, int stackTag, int stackData) { + writer.sameLocals(offsetDelta, stackTag, copyData(stackTag, stackData)); + } + + public void chopFrame(int pos, int offsetDelta, int k) { + writer.chopFrame(offsetDelta, k); + } + + public void appendFrame(int pos, int offsetDelta, int[] tags, int[] data) { + writer.appendFrame(offsetDelta, tags, copyData(tags, data)); + } + + public void fullFrame(int pos, int offsetDelta, int[] localTags, int[] localData, + int[] stackTags, int[] stackData) { + writer.fullFrame(offsetDelta, localTags, copyData(localTags, localData), + stackTags, copyData(stackTags, stackData)); + } + + protected int copyData(int tag, int data) { + return data; + } + + protected int[] copyData(int[] tags, int[] data) { + return data; + } + } + + static class Copier extends SimpleCopy { + private ConstPool srcPool, destPool; + + public Copier(ConstPool src, byte[] data, ConstPool dest) { + super(data); + srcPool = src; + destPool = dest; + } + + protected int copyData(int tag, int data) { + if (tag == OBJECT) + return srcPool.copy(data, destPool, null); + else + return data; + } + + protected int[] copyData(int[] tags, int[] data) { + int[] newData = new int[data.length]; + for (int i = 0; i < data.length; i++) + if (tags[i] == OBJECT) + newData[i] = srcPool.copy(data[i], destPool, null); + else + newData[i] = data[i]; + + return newData; + } + } + + /** + * Updates this stack map table when a new local variable is inserted + * for a new parameter. + * + * @param index the index of the added local variable. + * @param tag the type tag of that local variable. + * @param classInfo the index of the <code>CONSTANT_Class_info</code> structure + * in a constant pool table. This should be zero unless the tag + * is <code>ITEM_Object</code>. + * + * @see javassist.CtBehavior#addParameter(javassist.CtClass) + * @see #typeTagOf(char) + * @see ConstPool + */ + public void insertLocal(int index, int tag, int classInfo) + throws BadBytecode + { + byte[] data = new InsertLocal(this.get(), index, tag, classInfo).doit(); + this.set(data); + } + + /** + * Returns the tag of the type specified by the + * descriptor. This method returns <code>INTEGER</code> + * unless the descriptor is either D (double), F (float), + * J (long), L (class type), or [ (array). + * + * @param descriptor the type descriptor. + * @see Descriptor + */ + public static int typeTagOf(char descriptor) { + switch (descriptor) { + case 'D' : + return DOUBLE; + case 'F' : + return FLOAT; + case 'J' : + return LONG; + case 'L' : + case '[' : + return OBJECT; + // case 'V' : + default : + return INTEGER; + } + } + + /* This implementation assumes that a local variable initially + * holding a parameter value is never changed to be a different + * type. + * + */ + static class InsertLocal extends SimpleCopy { + private int varIndex; + private int varTag, varData; + + public InsertLocal(byte[] data, int varIndex, int varTag, int varData) { + super(data); + this.varIndex = varIndex; + this.varTag = varTag; + this.varData = varData; + } + + public void fullFrame(int pos, int offsetDelta, int[] localTags, int[] localData, + int[] stackTags, int[] stackData) { + int len = localTags.length; + if (len < varIndex) { + super.fullFrame(pos, offsetDelta, localTags, localData, stackTags, stackData); + return; + } + + int typeSize = (varTag == LONG || varTag == DOUBLE) ? 2 : 1; + int[] localTags2 = new int[len + typeSize]; + int[] localData2 = new int[len + typeSize]; + int index = varIndex; + int j = 0; + for (int i = 0; i < len; i++) { + if (j == index) + j += typeSize; + + localTags2[j] = localTags[i]; + localData2[j++] = localData[i]; + } + + localTags2[index] = varTag; + localData2[index] = varData; + if (typeSize > 1) { + localTags2[index + 1] = TOP; + localData2[index + 1] = 0; + } + + super.fullFrame(pos, offsetDelta, localTags2, localData2, stackTags, stackData); + } + } + + /** + * A writer of stack map tables. + */ + public static class Writer { + ByteArrayOutputStream output; + int numOfEntries; + + /** + * Constructs a writer. + * @param size the initial buffer size. + */ + public Writer(int size) { + output = new ByteArrayOutputStream(size); + numOfEntries = 0; + output.write(0); // u2 number_of_entries + output.write(0); + } + + /** + * Returns the stack map table written out. + */ + public byte[] toByteArray() { + byte[] b = output.toByteArray(); + ByteArray.write16bit(numOfEntries, b, 0); + return b; + } + + /** + * Constructs and a return a stack map table containing + * the written stack map entries. + * + * @param cp the constant pool used to write + * the stack map entries. + */ + public StackMapTable toStackMapTable(ConstPool cp) { + return new StackMapTable(cp, toByteArray()); + } + + /** + * Writes a <code>same_frame</code> or a <code>same_frame_extended</code>. + */ + public void sameFrame(int offsetDelta) { + numOfEntries++; + if (offsetDelta < 64) + output.write(offsetDelta); + else { + output.write(251); // SAME_FRAME_EXTENDED + write16(offsetDelta); + } + } + + /** + * Writes a <code>same_locals_1_stack_item</code> + * or a <code>same_locals_1_stack_item_extended</code>. + * + * @param tag <code>stack[0].tag</code>. + * @param data <code>stack[0].cpool_index</code> + * if the tag is <code>OBJECT</code>, + * or <cod>stack[0].offset</code> + * if the tag is <code>UNINIT</code>. + * Otherwise, this parameter is not used. + */ + public void sameLocals(int offsetDelta, int tag, int data) { + numOfEntries++; + if (offsetDelta < 64) + output.write(offsetDelta + 64); + else { + output.write(247); // SAME_LOCALS_1_STACK_ITEM_EXTENDED + write16(offsetDelta); + } + + writeTypeInfo(tag, data); + } + + /** + * Writes a <code>chop_frame</code>. + * + * @param k the number of absent locals. 1, 2, or 3. + */ + public void chopFrame(int offsetDelta, int k) { + numOfEntries++; + output.write(251 - k); + write16(offsetDelta); + } + + /** + * Writes a <code>append_frame</code>. The number of the appended + * locals is specified by the length of <code>tags</code>. + * + * @param tags <code>locals[].tag</code>. + * The length of this array must be + * either 1, 2, or 3. + * @param data <code>locals[].cpool_index</code> + * if the tag is <code>OBJECT</code>, + * or <cod>locals[].offset</code> + * if the tag is <code>UNINIT</code>. + * Otherwise, this parameter is not used. + */ + public void appendFrame(int offsetDelta, int[] tags, int[] data) { + numOfEntries++; + int k = tags.length; // k is 1, 2, or 3 + output.write(k + 251); + write16(offsetDelta); + for (int i = 0; i < k; i++) + writeTypeInfo(tags[i], data[i]); + } + + /** + * Writes a <code>full_frame</code>. + * <code>number_of_locals</code> and <code>number_of_stack_items</code> + * are specified by the the length of <code>localTags</code> and + * <code>stackTags</code>. + * + * @param localTags <code>locals[].tag</code>. + * @param localData <code>locals[].cpool_index</code> + * if the tag is <code>OBJECT</code>, + * or <cod>locals[].offset</code> + * if the tag is <code>UNINIT</code>. + * Otherwise, this parameter is not used. + * @param stackTags <code>stack[].tag</code>. + * @param stackData <code>stack[].cpool_index</code> + * if the tag is <code>OBJECT</code>, + * or <cod>stack[].offset</code> + * if the tag is <code>UNINIT</code>. + * Otherwise, this parameter is not used. + */ + public void fullFrame(int offsetDelta, int[] localTags, int[] localData, + int[] stackTags, int[] stackData) { + numOfEntries++; + output.write(255); // FULL_FRAME + write16(offsetDelta); + int n = localTags.length; + write16(n); + for (int i = 0; i < n; i++) + writeTypeInfo(localTags[i], localData[i]); + + n = stackTags.length; + write16(n); + for (int i = 0; i < n; i++) + writeTypeInfo(stackTags[i], stackData[i]); + } + + private void writeTypeInfo(int tag, int data) { + output.write(tag); + if (tag == OBJECT || tag == UNINIT) + write16(data); + } + + private void write16(int value) { + output.write((value >>> 8) & 0xff); + output.write(value & 0xff); + } + } + + /** + * Prints the stack table map. + */ + public void println(PrintWriter w) { + Printer.print(this, w); + } + + /** + * Prints the stack table map. + * + * @param ps a print stream such as <code>System.out</code>. + */ + public void println(java.io.PrintStream ps) { + Printer.print(this, new java.io.PrintWriter(ps, true)); + } + + static class Printer extends Walker { + private PrintWriter writer; + private int offset; + + /** + * Prints the stack table map. + */ + public static void print(StackMapTable smt, PrintWriter writer) { + try { + new Printer(smt.get(), writer).parse(); + } + catch (BadBytecode e) { + writer.println(e.getMessage()); + } + } + + Printer(byte[] data, PrintWriter pw) { + super(data); + writer = pw; + offset = -1; + } + + public void sameFrame(int pos, int offsetDelta) { + offset += offsetDelta + 1; + writer.println(offset + " same frame: " + offsetDelta); + } + + public void sameLocals(int pos, int offsetDelta, int stackTag, int stackData) { + offset += offsetDelta + 1; + writer.println(offset + " same locals: " + offsetDelta); + printTypeInfo(stackTag, stackData); + } + + public void chopFrame(int pos, int offsetDelta, int k) { + offset += offsetDelta + 1; + writer.println(offset + " chop frame: " + offsetDelta + ", " + k + " last locals"); + } + + public void appendFrame(int pos, int offsetDelta, int[] tags, int[] data) { + offset += offsetDelta + 1; + writer.println(offset + " append frame: " + offsetDelta); + for (int i = 0; i < tags.length; i++) + printTypeInfo(tags[i], data[i]); + } + + public void fullFrame(int pos, int offsetDelta, int[] localTags, int[] localData, + int[] stackTags, int[] stackData) { + offset += offsetDelta + 1; + writer.println(offset + " full frame: " + offsetDelta); + writer.println("[locals]"); + for (int i = 0; i < localTags.length; i++) + printTypeInfo(localTags[i], localData[i]); + + writer.println("[stack]"); + for (int i = 0; i < stackTags.length; i++) + printTypeInfo(stackTags[i], stackData[i]); + } + + private void printTypeInfo(int tag, int data) { + String msg = null; + switch (tag) { + case TOP : + msg = "top"; + break; + case INTEGER : + msg = "integer"; + break; + case FLOAT : + msg = "float"; + break; + case DOUBLE : + msg = "double"; + break; + case LONG : + msg = "long"; + break; + case NULL : + msg = "null"; + break; + case THIS : + msg = "this"; + break; + case OBJECT : + msg = "object (cpool_index " + data + ")"; + break; + case UNINIT : + msg = "uninitialized (offset " + data + ")"; + break; + } + + writer.print(" "); + writer.println(msg); + } + } + + void shiftPc(int where, int gapSize, boolean exclusive) + throws BadBytecode + { + new Shifter(this, where, gapSize, exclusive).doit(); + } + + static class Shifter extends Walker { + private StackMapTable stackMap; + private int where, gap; + private int position; + private byte[] updatedInfo; + private boolean exclusive; + + public Shifter(StackMapTable smt, int where, int gap, boolean exclusive) { + super(smt); + stackMap = smt; + this.where = where; + this.gap = gap; + this.position = 0; + this.updatedInfo = null; + this.exclusive = exclusive; + } + + public void doit() throws BadBytecode { + parse(); + if (updatedInfo != null) + stackMap.set(updatedInfo); + } + + public void sameFrame(int pos, int offsetDelta) { + update(pos, offsetDelta, 0, 251); + } + + public void sameLocals(int pos, int offsetDelta, int stackTag, int stackData) { + update(pos, offsetDelta, 64, 247); + } + + private void update(int pos, int offsetDelta, int base, int entry) { + int oldPos = position; + position = oldPos + offsetDelta + (oldPos == 0 ? 0 : 1); + boolean match; + if (exclusive) + match = oldPos < where && where <= position; + else + match = oldPos <= where && where < position; + + if (match) { + int newDelta = offsetDelta + gap; + position += gap; + if (newDelta < 64) + info[pos] = (byte)(newDelta + base); + else if (offsetDelta < 64) { + byte[] newinfo = insertGap(info, pos, 2); + newinfo[pos] = (byte)entry; + ByteArray.write16bit(newDelta, newinfo, pos + 1); + updatedInfo = newinfo; + } + else + ByteArray.write16bit(newDelta, info, pos + 1); + } + } + + private static byte[] insertGap(byte[] info, int where, int gap) { + int len = info.length; + byte[] newinfo = new byte[len + gap]; + for (int i = 0; i < len; i++) + newinfo[i + (i < where ? 0 : gap)] = info[i]; + + return newinfo; + } + + public void chopFrame(int pos, int offsetDelta, int k) { + update(pos, offsetDelta); + } + + public void appendFrame(int pos, int offsetDelta, int[] tags, int[] data) { + update(pos, offsetDelta); + } + + public void fullFrame(int pos, int offsetDelta, int[] localTags, int[] localData, + int[] stackTags, int[] stackData) { + update(pos, offsetDelta); + } + + private void update(int pos, int offsetDelta) { + int oldPos = position; + position = oldPos + offsetDelta + (oldPos == 0 ? 0 : 1); + boolean match; + if (exclusive) + match = oldPos < where && where <= position; + else + match = oldPos <= where && where < position; + + if (match) { + int newDelta = offsetDelta + gap; + ByteArray.write16bit(newDelta, info, pos + 1); + position += gap; + } + } + } + + /** + * Undocumented method. Do not use; internal-use only. + * + * <p>This method is for javassist.convert.TransformNew. + * It is called to update the stack map table when + * the NEW opcode (and the following DUP) is removed. + * + * @param where the position of the removed NEW opcode. + */ + public void removeNew(int where) throws CannotCompileException { + try { + byte[] data = new NewRemover(this.get(), where).doit(); + this.set(data); + } + catch (BadBytecode e) { + throw new CannotCompileException("bad stack map table", e); + } + } + + static class NewRemover extends SimpleCopy { + int posOfNew; + + public NewRemover(byte[] data, int pos) { + super(data); + posOfNew = pos; + } + + public void sameLocals(int pos, int offsetDelta, int stackTag, int stackData) { + if (stackTag == UNINIT && stackData == posOfNew) + super.sameFrame(pos, offsetDelta); + else + super.sameLocals(pos, offsetDelta, stackTag, stackData); + } + + public void fullFrame(int pos, int offsetDelta, int[] localTags, int[] localData, + int[] stackTags, int[] stackData) { + int n = stackTags.length - 1; + for (int i = 0; i < n; i++) + if (stackTags[i] == UNINIT && stackData[i] == posOfNew + && stackTags[i + 1] == UNINIT && stackData[i + 1] == posOfNew) { + n++; + int[] stackTags2 = new int[n - 2]; + int[] stackData2 = new int[n - 2]; + int k = 0; + for (int j = 0; j < n; j++) + if (j == i) + j++; + else { + stackTags2[k] = stackTags[j]; + stackData2[k++] = stackData[j]; + } + + stackTags = stackTags2; + stackData = stackData2; + break; + } + + super.fullFrame(pos, offsetDelta, localTags, localData, stackTags, stackData); + } + } +} diff --git a/src/main/javassist/bytecode/SyntheticAttribute.java b/src/main/javassist/bytecode/SyntheticAttribute.java new file mode 100644 index 0000000..8cc697d --- /dev/null +++ b/src/main/javassist/bytecode/SyntheticAttribute.java @@ -0,0 +1,55 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.bytecode; + +import java.io.DataInputStream; +import java.io.IOException; +import java.util.Map; + +/** + * <code>Synthetic_attribute</code>. + */ +public class SyntheticAttribute extends AttributeInfo { + /** + * The name of this attribute <code>"Synthetic"</code>. + */ + public static final String tag = "Synthetic"; + + SyntheticAttribute(ConstPool cp, int n, DataInputStream in) + throws IOException + { + super(cp, n, in); + } + + /** + * Constructs a Synthetic attribute. + * + * @param cp a constant pool table. + */ + public SyntheticAttribute(ConstPool cp) { + super(cp, tag, new byte[0]); + } + + /** + * Makes a copy. + * + * @param newCp the constant pool table used by the new copy. + * @param classnames should be null. + */ + public AttributeInfo copy(ConstPool newCp, Map classnames) { + return new SyntheticAttribute(newCp); + } +} diff --git a/src/main/javassist/bytecode/analysis/Analyzer.java b/src/main/javassist/bytecode/analysis/Analyzer.java new file mode 100644 index 0000000..ea56599 --- /dev/null +++ b/src/main/javassist/bytecode/analysis/Analyzer.java @@ -0,0 +1,422 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba, and others. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ +package javassist.bytecode.analysis; + +import java.util.Iterator; + +import javassist.ClassPool; +import javassist.CtClass; +import javassist.CtMethod; +import javassist.NotFoundException; +import javassist.bytecode.AccessFlag; +import javassist.bytecode.BadBytecode; +import javassist.bytecode.CodeAttribute; +import javassist.bytecode.CodeIterator; +import javassist.bytecode.ConstPool; +import javassist.bytecode.Descriptor; +import javassist.bytecode.ExceptionTable; +import javassist.bytecode.MethodInfo; +import javassist.bytecode.Opcode; + +/** + * A data-flow analyzer that determines the type state of the stack and local + * variable table at every reachable instruction in a method. During analysis, + * bytecode verification is performed in a similar manner to that described + * in the JVM specification. + * + * <p>Example:</p> + * + * <pre> + * // Method to analyze + * public Object doSomething(int x) { + * Number n; + * if (x < 5) { + * n = new Double(0); + * } else { + * n = new Long(0); + * } + * + * return n; + * } + * + * // Which compiles to: + * // 0: iload_1 + * // 1: iconst_5 + * // 2: if_icmpge 17 + * // 5: new #18; //class java/lang/Double + * // 8: dup + * // 9: dconst_0 + * // 10: invokespecial #44; //Method java/lang/Double."<init>":(D)V + * // 13: astore_2 + * // 14: goto 26 + * // 17: new #16; //class java/lang/Long + * // 20: dup + * // 21: lconst_1 + * // 22: invokespecial #47; //Method java/lang/Long."<init>":(J)V + * // 25: astore_2 + * // 26: aload_2 + * // 27: areturn + * + * public void analyzeIt(CtClass clazz, MethodInfo method) { + * Analyzer analyzer = new Analyzer(); + * Frame[] frames = analyzer.analyze(clazz, method); + * frames[0].getLocal(0).getCtClass(); // returns clazz; + * frames[0].getLocal(1).getCtClass(); // returns java.lang.String + * frames[1].peek(); // returns Type.INTEGER + * frames[27].peek().getCtClass(); // returns java.lang.Number + * } + * </pre> + * + * @see FramePrinter + * @author Jason T. Greene + */ +public class Analyzer implements Opcode { + private final SubroutineScanner scanner = new SubroutineScanner(); + private CtClass clazz; + private ExceptionInfo[] exceptions; + private Frame[] frames; + private Subroutine[] subroutines; + + private static class ExceptionInfo { + private int end; + private int handler; + private int start; + private Type type; + + private ExceptionInfo(int start, int end, int handler, Type type) { + this.start = start; + this.end = end; + this.handler = handler; + this.type = type; + } + } + + /** + * Performs data-flow analysis on a method and returns an array, indexed by + * instruction position, containing the starting frame state of all reachable + * instructions. Non-reachable code, and illegal code offsets are represented + * as a null in the frame state array. This can be used to detect dead code. + * + * If the method does not contain code (it is either native or abstract), null + * is returned. + * + * @param clazz the declaring class of the method + * @param method the method to analyze + * @return an array, indexed by instruction position, of the starting frame state, + * or null if this method doesn't have code + * @throws BadBytecode if the bytecode does not comply with the JVM specification + */ + public Frame[] analyze(CtClass clazz, MethodInfo method) throws BadBytecode { + this.clazz = clazz; + CodeAttribute codeAttribute = method.getCodeAttribute(); + // Native or Abstract + if (codeAttribute == null) + return null; + + int maxLocals = codeAttribute.getMaxLocals(); + int maxStack = codeAttribute.getMaxStack(); + int codeLength = codeAttribute.getCodeLength(); + + CodeIterator iter = codeAttribute.iterator(); + IntQueue queue = new IntQueue(); + + exceptions = buildExceptionInfo(method); + subroutines = scanner.scan(method); + + Executor executor = new Executor(clazz.getClassPool(), method.getConstPool()); + frames = new Frame[codeLength]; + frames[iter.lookAhead()] = firstFrame(method, maxLocals, maxStack); + queue.add(iter.next()); + while (!queue.isEmpty()) { + analyzeNextEntry(method, iter, queue, executor); + } + + return frames; + } + + /** + * Performs data-flow analysis on a method and returns an array, indexed by + * instruction position, containing the starting frame state of all reachable + * instructions. Non-reachable code, and illegal code offsets are represented + * as a null in the frame state array. This can be used to detect dead code. + * + * If the method does not contain code (it is either native or abstract), null + * is returned. + * + * @param method the method to analyze + * @return an array, indexed by instruction position, of the starting frame state, + * or null if this method doesn't have code + * @throws BadBytecode if the bytecode does not comply with the JVM specification + */ + public Frame[] analyze(CtMethod method) throws BadBytecode { + return analyze(method.getDeclaringClass(), method.getMethodInfo2()); + } + + private void analyzeNextEntry(MethodInfo method, CodeIterator iter, + IntQueue queue, Executor executor) throws BadBytecode { + int pos = queue.take(); + iter.move(pos); + iter.next(); + + Frame frame = frames[pos].copy(); + Subroutine subroutine = subroutines[pos]; + + try { + executor.execute(method, pos, iter, frame, subroutine); + } catch (RuntimeException e) { + throw new BadBytecode(e.getMessage() + "[pos = " + pos + "]", e); + } + + int opcode = iter.byteAt(pos); + + if (opcode == TABLESWITCH) { + mergeTableSwitch(queue, pos, iter, frame); + } else if (opcode == LOOKUPSWITCH) { + mergeLookupSwitch(queue, pos, iter, frame); + } else if (opcode == RET) { + mergeRet(queue, iter, pos, frame, subroutine); + } else if (Util.isJumpInstruction(opcode)) { + int target = Util.getJumpTarget(pos, iter); + + if (Util.isJsr(opcode)) { + // Merge the state before the jsr into the next instruction + mergeJsr(queue, frames[pos], subroutines[target], pos, lookAhead(iter, pos)); + } else if (! Util.isGoto(opcode)) { + merge(queue, frame, lookAhead(iter, pos)); + } + + merge(queue, frame, target); + } else if (opcode != ATHROW && ! Util.isReturn(opcode)) { + // Can advance to next instruction + merge(queue, frame, lookAhead(iter, pos)); + } + + // Merge all exceptions that are reachable from this instruction. + // The redundancy is intentional, since the state must be based + // on the current instruction frame. + mergeExceptionHandlers(queue, method, pos, frame); + } + + private ExceptionInfo[] buildExceptionInfo(MethodInfo method) { + ConstPool constPool = method.getConstPool(); + ClassPool classes = clazz.getClassPool(); + + ExceptionTable table = method.getCodeAttribute().getExceptionTable(); + ExceptionInfo[] exceptions = new ExceptionInfo[table.size()]; + for (int i = 0; i < table.size(); i++) { + int index = table.catchType(i); + Type type; + try { + type = index == 0 ? Type.THROWABLE : Type.get(classes.get(constPool.getClassInfo(index))); + } catch (NotFoundException e) { + throw new IllegalStateException(e.getMessage()); + } + + exceptions[i] = new ExceptionInfo(table.startPc(i), table.endPc(i), table.handlerPc(i), type); + } + + return exceptions; + } + + private Frame firstFrame(MethodInfo method, int maxLocals, int maxStack) { + int pos = 0; + + Frame first = new Frame(maxLocals, maxStack); + if ((method.getAccessFlags() & AccessFlag.STATIC) == 0) { + first.setLocal(pos++, Type.get(clazz)); + } + + CtClass[] parameters; + try { + parameters = Descriptor.getParameterTypes(method.getDescriptor(), clazz.getClassPool()); + } catch (NotFoundException e) { + throw new RuntimeException(e); + } + + for (int i = 0; i < parameters.length; i++) { + Type type = zeroExtend(Type.get(parameters[i])); + first.setLocal(pos++, type); + if (type.getSize() == 2) + first.setLocal(pos++, Type.TOP); + } + + return first; + } + + private int getNext(CodeIterator iter, int of, int restore) throws BadBytecode { + iter.move(of); + iter.next(); + int next = iter.lookAhead(); + iter.move(restore); + iter.next(); + + return next; + } + + private int lookAhead(CodeIterator iter, int pos) throws BadBytecode { + if (! iter.hasNext()) + throw new BadBytecode("Execution falls off end! [pos = " + pos + "]"); + + return iter.lookAhead(); + } + + + private void merge(IntQueue queue, Frame frame, int target) { + Frame old = frames[target]; + boolean changed; + + if (old == null) { + frames[target] = frame.copy(); + changed = true; + } else { + changed = old.merge(frame); + } + + if (changed) { + queue.add(target); + } + } + + private void mergeExceptionHandlers(IntQueue queue, MethodInfo method, int pos, Frame frame) { + for (int i = 0; i < exceptions.length; i++) { + ExceptionInfo exception = exceptions[i]; + + // Start is inclusive, while end is exclusive! + if (pos >= exception.start && pos < exception.end) { + Frame newFrame = frame.copy(); + newFrame.clearStack(); + newFrame.push(exception.type); + merge(queue, newFrame, exception.handler); + } + } + } + + private void mergeJsr(IntQueue queue, Frame frame, Subroutine sub, int pos, int next) throws BadBytecode { + if (sub == null) + throw new BadBytecode("No subroutine at jsr target! [pos = " + pos + "]"); + + Frame old = frames[next]; + boolean changed = false; + + if (old == null) { + old = frames[next] = frame.copy(); + changed = true; + } else { + for (int i = 0; i < frame.localsLength(); i++) { + // Skip everything accessed by a subroutine, mergeRet must handle this + if (!sub.isAccessed(i)) { + Type oldType = old.getLocal(i); + Type newType = frame.getLocal(i); + if (oldType == null) { + old.setLocal(i, newType); + changed = true; + continue; + } + + newType = oldType.merge(newType); + // Always set the type, in case a multi-type switched to a standard type. + old.setLocal(i, newType); + if (!newType.equals(oldType) || newType.popChanged()) + changed = true; + } + } + } + + if (! old.isJsrMerged()) { + old.setJsrMerged(true); + changed = true; + } + + if (changed && old.isRetMerged()) + queue.add(next); + + } + + private void mergeLookupSwitch(IntQueue queue, int pos, CodeIterator iter, Frame frame) throws BadBytecode { + int index = (pos & ~3) + 4; + // default + merge(queue, frame, pos + iter.s32bitAt(index)); + int npairs = iter.s32bitAt(index += 4); + int end = npairs * 8 + (index += 4); + + // skip "match" + for (index += 4; index < end; index += 8) { + int target = iter.s32bitAt(index) + pos; + merge(queue, frame, target); + } + } + + private void mergeRet(IntQueue queue, CodeIterator iter, int pos, Frame frame, Subroutine subroutine) throws BadBytecode { + if (subroutine == null) + throw new BadBytecode("Ret on no subroutine! [pos = " + pos + "]"); + + Iterator callerIter = subroutine.callers().iterator(); + while (callerIter.hasNext()) { + int caller = ((Integer) callerIter.next()).intValue(); + int returnLoc = getNext(iter, caller, pos); + boolean changed = false; + + Frame old = frames[returnLoc]; + if (old == null) { + old = frames[returnLoc] = frame.copyStack(); + changed = true; + } else { + changed = old.mergeStack(frame); + } + + for (Iterator i = subroutine.accessed().iterator(); i.hasNext(); ) { + int index = ((Integer)i.next()).intValue(); + Type oldType = old.getLocal(index); + Type newType = frame.getLocal(index); + if (oldType != newType) { + old.setLocal(index, newType); + changed = true; + } + } + + if (! old.isRetMerged()) { + old.setRetMerged(true); + changed = true; + } + + if (changed && old.isJsrMerged()) + queue.add(returnLoc); + } + } + + + private void mergeTableSwitch(IntQueue queue, int pos, CodeIterator iter, Frame frame) throws BadBytecode { + // Skip 4 byte alignment padding + int index = (pos & ~3) + 4; + // default + merge(queue, frame, pos + iter.s32bitAt(index)); + int low = iter.s32bitAt(index += 4); + int high = iter.s32bitAt(index += 4); + int end = (high - low + 1) * 4 + (index += 4); + + // Offset table + for (; index < end; index += 4) { + int target = iter.s32bitAt(index) + pos; + merge(queue, frame, target); + } + } + + private Type zeroExtend(Type type) { + if (type == Type.SHORT || type == Type.BYTE || type == Type.CHAR || type == Type.BOOLEAN) + return Type.INTEGER; + + return type; + } +} diff --git a/src/main/javassist/bytecode/analysis/Executor.java b/src/main/javassist/bytecode/analysis/Executor.java new file mode 100644 index 0000000..562cc5d --- /dev/null +++ b/src/main/javassist/bytecode/analysis/Executor.java @@ -0,0 +1,1031 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba, and others. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ +package javassist.bytecode.analysis; + +import javassist.ClassPool; +import javassist.CtClass; +import javassist.NotFoundException; +import javassist.bytecode.BadBytecode; +import javassist.bytecode.CodeIterator; +import javassist.bytecode.ConstPool; +import javassist.bytecode.Descriptor; +import javassist.bytecode.MethodInfo; +import javassist.bytecode.Opcode; + +/** + * Executor is responsible for modeling the effects of a JVM instruction on a frame. + * + * @author Jason T. Greene + */ +public class Executor implements Opcode { + private final ConstPool constPool; + private final ClassPool classPool; + private final Type STRING_TYPE; + private final Type CLASS_TYPE; + private final Type THROWABLE_TYPE; + private int lastPos; + + public Executor(ClassPool classPool, ConstPool constPool) { + this.constPool = constPool; + this.classPool = classPool; + + try { + STRING_TYPE = getType("java.lang.String"); + CLASS_TYPE = getType("java.lang.Class"); + THROWABLE_TYPE = getType("java.lang.Throwable"); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + + /** + * Execute the instruction, modeling the effects on the specified frame and subroutine. + * If a subroutine is passed, the access flags will be modified if this instruction accesses + * the local variable table. + * + * @param method the method containing the instruction + * @param pos the position of the instruction in the method + * @param iter the code iterator used to find the instruction + * @param frame the frame to modify to represent the result of the instruction + * @param subroutine the optional subroutine this instruction belongs to. + * @throws BadBytecode if the bytecode violates the jvm spec + */ + public void execute(MethodInfo method, int pos, CodeIterator iter, Frame frame, Subroutine subroutine) throws BadBytecode { + this.lastPos = pos; + int opcode = iter.byteAt(pos); + + + // Declared opcode in order + switch (opcode) { + case NOP: + break; + case ACONST_NULL: + frame.push(Type.UNINIT); + break; + case ICONST_M1: + case ICONST_0: + case ICONST_1: + case ICONST_2: + case ICONST_3: + case ICONST_4: + case ICONST_5: + frame.push(Type.INTEGER); + break; + case LCONST_0: + case LCONST_1: + frame.push(Type.LONG); + frame.push(Type.TOP); + break; + case FCONST_0: + case FCONST_1: + case FCONST_2: + frame.push(Type.FLOAT); + break; + case DCONST_0: + case DCONST_1: + frame.push(Type.DOUBLE); + frame.push(Type.TOP); + break; + case BIPUSH: + case SIPUSH: + frame.push(Type.INTEGER); + break; + case LDC: + evalLDC(iter.byteAt(pos + 1), frame); + break; + case LDC_W : + case LDC2_W : + evalLDC(iter.u16bitAt(pos + 1), frame); + break; + case ILOAD: + evalLoad(Type.INTEGER, iter.byteAt(pos + 1), frame, subroutine); + break; + case LLOAD: + evalLoad(Type.LONG, iter.byteAt(pos + 1), frame, subroutine); + break; + case FLOAD: + evalLoad(Type.FLOAT, iter.byteAt(pos + 1), frame, subroutine); + break; + case DLOAD: + evalLoad(Type.DOUBLE, iter.byteAt(pos + 1), frame, subroutine); + break; + case ALOAD: + evalLoad(Type.OBJECT, iter.byteAt(pos + 1), frame, subroutine); + break; + case ILOAD_0: + case ILOAD_1: + case ILOAD_2: + case ILOAD_3: + evalLoad(Type.INTEGER, opcode - ILOAD_0, frame, subroutine); + break; + case LLOAD_0: + case LLOAD_1: + case LLOAD_2: + case LLOAD_3: + evalLoad(Type.LONG, opcode - LLOAD_0, frame, subroutine); + break; + case FLOAD_0: + case FLOAD_1: + case FLOAD_2: + case FLOAD_3: + evalLoad(Type.FLOAT, opcode - FLOAD_0, frame, subroutine); + break; + case DLOAD_0: + case DLOAD_1: + case DLOAD_2: + case DLOAD_3: + evalLoad(Type.DOUBLE, opcode - DLOAD_0, frame, subroutine); + break; + case ALOAD_0: + case ALOAD_1: + case ALOAD_2: + case ALOAD_3: + evalLoad(Type.OBJECT, opcode - ALOAD_0, frame, subroutine); + break; + case IALOAD: + evalArrayLoad(Type.INTEGER, frame); + break; + case LALOAD: + evalArrayLoad(Type.LONG, frame); + break; + case FALOAD: + evalArrayLoad(Type.FLOAT, frame); + break; + case DALOAD: + evalArrayLoad(Type.DOUBLE, frame); + break; + case AALOAD: + evalArrayLoad(Type.OBJECT, frame); + break; + case BALOAD: + case CALOAD: + case SALOAD: + evalArrayLoad(Type.INTEGER, frame); + break; + case ISTORE: + evalStore(Type.INTEGER, iter.byteAt(pos + 1), frame, subroutine); + break; + case LSTORE: + evalStore(Type.LONG, iter.byteAt(pos + 1), frame, subroutine); + break; + case FSTORE: + evalStore(Type.FLOAT, iter.byteAt(pos + 1), frame, subroutine); + break; + case DSTORE: + evalStore(Type.DOUBLE, iter.byteAt(pos + 1), frame, subroutine); + break; + case ASTORE: + evalStore(Type.OBJECT, iter.byteAt(pos + 1), frame, subroutine); + break; + case ISTORE_0: + case ISTORE_1: + case ISTORE_2: + case ISTORE_3: + evalStore(Type.INTEGER, opcode - ISTORE_0, frame, subroutine); + break; + case LSTORE_0: + case LSTORE_1: + case LSTORE_2: + case LSTORE_3: + evalStore(Type.LONG, opcode - LSTORE_0, frame, subroutine); + break; + case FSTORE_0: + case FSTORE_1: + case FSTORE_2: + case FSTORE_3: + evalStore(Type.FLOAT, opcode - FSTORE_0, frame, subroutine); + break; + case DSTORE_0: + case DSTORE_1: + case DSTORE_2: + case DSTORE_3: + evalStore(Type.DOUBLE, opcode - DSTORE_0, frame, subroutine); + break; + case ASTORE_0: + case ASTORE_1: + case ASTORE_2: + case ASTORE_3: + evalStore(Type.OBJECT, opcode - ASTORE_0, frame, subroutine); + break; + case IASTORE: + evalArrayStore(Type.INTEGER, frame); + break; + case LASTORE: + evalArrayStore(Type.LONG, frame); + break; + case FASTORE: + evalArrayStore(Type.FLOAT, frame); + break; + case DASTORE: + evalArrayStore(Type.DOUBLE, frame); + break; + case AASTORE: + evalArrayStore(Type.OBJECT, frame); + break; + case BASTORE: + case CASTORE: + case SASTORE: + evalArrayStore(Type.INTEGER, frame); + break; + case POP: + if (frame.pop() == Type.TOP) + throw new BadBytecode("POP can not be used with a category 2 value, pos = " + pos); + break; + case POP2: + frame.pop(); + frame.pop(); + break; + case DUP: { + Type type = frame.peek(); + if (type == Type.TOP) + throw new BadBytecode("DUP can not be used with a category 2 value, pos = " + pos); + + frame.push(frame.peek()); + break; + } + case DUP_X1: + case DUP_X2: { + Type type = frame.peek(); + if (type == Type.TOP) + throw new BadBytecode("DUP can not be used with a category 2 value, pos = " + pos); + int end = frame.getTopIndex(); + int insert = end - (opcode - DUP_X1) - 1; + frame.push(type); + + while (end > insert) { + frame.setStack(end, frame.getStack(end - 1)); + end--; + } + frame.setStack(insert, type); + break; + } + case DUP2: + frame.push(frame.getStack(frame.getTopIndex() - 1)); + frame.push(frame.getStack(frame.getTopIndex() - 1)); + break; + case DUP2_X1: + case DUP2_X2: { + int end = frame.getTopIndex(); + int insert = end - (opcode - DUP2_X1) - 1; + Type type1 = frame.getStack(frame.getTopIndex() - 1); + Type type2 = frame.peek(); + frame.push(type1); + frame.push(type2); + while (end > insert) { + frame.setStack(end, frame.getStack(end - 2)); + end--; + } + frame.setStack(insert, type2); + frame.setStack(insert - 1, type1); + break; + } + case SWAP: { + Type type1 = frame.pop(); + Type type2 = frame.pop(); + if (type1.getSize() == 2 || type2.getSize() == 2) + throw new BadBytecode("Swap can not be used with category 2 values, pos = " + pos); + frame.push(type1); + frame.push(type2); + break; + } + + // Math + case IADD: + evalBinaryMath(Type.INTEGER, frame); + break; + case LADD: + evalBinaryMath(Type.LONG, frame); + break; + case FADD: + evalBinaryMath(Type.FLOAT, frame); + break; + case DADD: + evalBinaryMath(Type.DOUBLE, frame); + break; + case ISUB: + evalBinaryMath(Type.INTEGER, frame); + break; + case LSUB: + evalBinaryMath(Type.LONG, frame); + break; + case FSUB: + evalBinaryMath(Type.FLOAT, frame); + break; + case DSUB: + evalBinaryMath(Type.DOUBLE, frame); + break; + case IMUL: + evalBinaryMath(Type.INTEGER, frame); + break; + case LMUL: + evalBinaryMath(Type.LONG, frame); + break; + case FMUL: + evalBinaryMath(Type.FLOAT, frame); + break; + case DMUL: + evalBinaryMath(Type.DOUBLE, frame); + break; + case IDIV: + evalBinaryMath(Type.INTEGER, frame); + break; + case LDIV: + evalBinaryMath(Type.LONG, frame); + break; + case FDIV: + evalBinaryMath(Type.FLOAT, frame); + break; + case DDIV: + evalBinaryMath(Type.DOUBLE, frame); + break; + case IREM: + evalBinaryMath(Type.INTEGER, frame); + break; + case LREM: + evalBinaryMath(Type.LONG, frame); + break; + case FREM: + evalBinaryMath(Type.FLOAT, frame); + break; + case DREM: + evalBinaryMath(Type.DOUBLE, frame); + break; + + // Unary + case INEG: + verifyAssignable(Type.INTEGER, simplePeek(frame)); + break; + case LNEG: + verifyAssignable(Type.LONG, simplePeek(frame)); + break; + case FNEG: + verifyAssignable(Type.FLOAT, simplePeek(frame)); + break; + case DNEG: + verifyAssignable(Type.DOUBLE, simplePeek(frame)); + break; + + // Shifts + case ISHL: + evalShift(Type.INTEGER, frame); + break; + case LSHL: + evalShift(Type.LONG, frame); + break; + case ISHR: + evalShift(Type.INTEGER, frame); + break; + case LSHR: + evalShift(Type.LONG, frame); + break; + case IUSHR: + evalShift(Type.INTEGER,frame); + break; + case LUSHR: + evalShift(Type.LONG, frame); + break; + + // Bitwise Math + case IAND: + evalBinaryMath(Type.INTEGER, frame); + break; + case LAND: + evalBinaryMath(Type.LONG, frame); + break; + case IOR: + evalBinaryMath(Type.INTEGER, frame); + break; + case LOR: + evalBinaryMath(Type.LONG, frame); + break; + case IXOR: + evalBinaryMath(Type.INTEGER, frame); + break; + case LXOR: + evalBinaryMath(Type.LONG, frame); + break; + + case IINC: { + int index = iter.byteAt(pos + 1); + verifyAssignable(Type.INTEGER, frame.getLocal(index)); + access(index, Type.INTEGER, subroutine); + break; + } + + // Conversion + case I2L: + verifyAssignable(Type.INTEGER, simplePop(frame)); + simplePush(Type.LONG, frame); + break; + case I2F: + verifyAssignable(Type.INTEGER, simplePop(frame)); + simplePush(Type.FLOAT, frame); + break; + case I2D: + verifyAssignable(Type.INTEGER, simplePop(frame)); + simplePush(Type.DOUBLE, frame); + break; + case L2I: + verifyAssignable(Type.LONG, simplePop(frame)); + simplePush(Type.INTEGER, frame); + break; + case L2F: + verifyAssignable(Type.LONG, simplePop(frame)); + simplePush(Type.FLOAT, frame); + break; + case L2D: + verifyAssignable(Type.LONG, simplePop(frame)); + simplePush(Type.DOUBLE, frame); + break; + case F2I: + verifyAssignable(Type.FLOAT, simplePop(frame)); + simplePush(Type.INTEGER, frame); + break; + case F2L: + verifyAssignable(Type.FLOAT, simplePop(frame)); + simplePush(Type.LONG, frame); + break; + case F2D: + verifyAssignable(Type.FLOAT, simplePop(frame)); + simplePush(Type.DOUBLE, frame); + break; + case D2I: + verifyAssignable(Type.DOUBLE, simplePop(frame)); + simplePush(Type.INTEGER, frame); + break; + case D2L: + verifyAssignable(Type.DOUBLE, simplePop(frame)); + simplePush(Type.LONG, frame); + break; + case D2F: + verifyAssignable(Type.DOUBLE, simplePop(frame)); + simplePush(Type.FLOAT, frame); + break; + case I2B: + case I2C: + case I2S: + verifyAssignable(Type.INTEGER, frame.peek()); + break; + case LCMP: + verifyAssignable(Type.LONG, simplePop(frame)); + verifyAssignable(Type.LONG, simplePop(frame)); + frame.push(Type.INTEGER); + break; + case FCMPL: + case FCMPG: + verifyAssignable(Type.FLOAT, simplePop(frame)); + verifyAssignable(Type.FLOAT, simplePop(frame)); + frame.push(Type.INTEGER); + break; + case DCMPL: + case DCMPG: + verifyAssignable(Type.DOUBLE, simplePop(frame)); + verifyAssignable(Type.DOUBLE, simplePop(frame)); + frame.push(Type.INTEGER); + break; + + // Control flow + case IFEQ: + case IFNE: + case IFLT: + case IFGE: + case IFGT: + case IFLE: + verifyAssignable(Type.INTEGER, simplePop(frame)); + break; + case IF_ICMPEQ: + case IF_ICMPNE: + case IF_ICMPLT: + case IF_ICMPGE: + case IF_ICMPGT: + case IF_ICMPLE: + verifyAssignable(Type.INTEGER, simplePop(frame)); + verifyAssignable(Type.INTEGER, simplePop(frame)); + break; + case IF_ACMPEQ: + case IF_ACMPNE: + verifyAssignable(Type.OBJECT, simplePop(frame)); + verifyAssignable(Type.OBJECT, simplePop(frame)); + break; + case GOTO: + break; + case JSR: + frame.push(Type.RETURN_ADDRESS); + break; + case RET: + verifyAssignable(Type.RETURN_ADDRESS, frame.getLocal(iter.byteAt(pos + 1))); + break; + case TABLESWITCH: + case LOOKUPSWITCH: + case IRETURN: + verifyAssignable(Type.INTEGER, simplePop(frame)); + break; + case LRETURN: + verifyAssignable(Type.LONG, simplePop(frame)); + break; + case FRETURN: + verifyAssignable(Type.FLOAT, simplePop(frame)); + break; + case DRETURN: + verifyAssignable(Type.DOUBLE, simplePop(frame)); + break; + case ARETURN: + try { + CtClass returnType = Descriptor.getReturnType(method.getDescriptor(), classPool); + verifyAssignable(Type.get(returnType), simplePop(frame)); + } catch (NotFoundException e) { + throw new RuntimeException(e); + } + break; + case RETURN: + break; + case GETSTATIC: + evalGetField(opcode, iter.u16bitAt(pos + 1), frame); + break; + case PUTSTATIC: + evalPutField(opcode, iter.u16bitAt(pos + 1), frame); + break; + case GETFIELD: + evalGetField(opcode, iter.u16bitAt(pos + 1), frame); + break; + case PUTFIELD: + evalPutField(opcode, iter.u16bitAt(pos + 1), frame); + break; + case INVOKEVIRTUAL: + case INVOKESPECIAL: + case INVOKESTATIC: + evalInvokeMethod(opcode, iter.u16bitAt(pos + 1), frame); + break; + case INVOKEINTERFACE: + evalInvokeIntfMethod(opcode, iter.u16bitAt(pos + 1), frame); + break; + case 186: + throw new RuntimeException("Bad opcode 186"); + case NEW: + frame.push(resolveClassInfo(constPool.getClassInfo(iter.u16bitAt(pos + 1)))); + break; + case NEWARRAY: + evalNewArray(pos, iter, frame); + break; + case ANEWARRAY: + evalNewObjectArray(pos, iter, frame); + break; + case ARRAYLENGTH: { + Type array = simplePop(frame); + if (! array.isArray() && array != Type.UNINIT) + throw new BadBytecode("Array length passed a non-array [pos = " + pos + "]: " + array); + frame.push(Type.INTEGER); + break; + } + case ATHROW: + verifyAssignable(THROWABLE_TYPE, simplePop(frame)); + break; + case CHECKCAST: + verifyAssignable(Type.OBJECT, simplePop(frame)); + frame.push(typeFromDesc(constPool.getClassInfo(iter.u16bitAt(pos + 1)))); + break; + case INSTANCEOF: + verifyAssignable(Type.OBJECT, simplePop(frame)); + frame.push(Type.INTEGER); + break; + case MONITORENTER: + case MONITOREXIT: + verifyAssignable(Type.OBJECT, simplePop(frame)); + break; + case WIDE: + evalWide(pos, iter, frame, subroutine); + break; + case MULTIANEWARRAY: + evalNewObjectArray(pos, iter, frame); + break; + case IFNULL: + case IFNONNULL: + verifyAssignable(Type.OBJECT, simplePop(frame)); + break; + case GOTO_W: + break; + case JSR_W: + frame.push(Type.RETURN_ADDRESS); + break; + } + } + + private Type zeroExtend(Type type) { + if (type == Type.SHORT || type == Type.BYTE || type == Type.CHAR || type == Type.BOOLEAN) + return Type.INTEGER; + + return type; + } + + private void evalArrayLoad(Type expectedComponent, Frame frame) throws BadBytecode { + Type index = frame.pop(); + Type array = frame.pop(); + + // Special case, an array defined by aconst_null + // TODO - we might need to be more inteligent about this + if (array == Type.UNINIT) { + verifyAssignable(Type.INTEGER, index); + if (expectedComponent == Type.OBJECT) { + simplePush(Type.UNINIT, frame); + } else { + simplePush(expectedComponent, frame); + } + return; + } + + Type component = array.getComponent(); + + if (component == null) + throw new BadBytecode("Not an array! [pos = " + lastPos + "]: " + component); + + component = zeroExtend(component); + + verifyAssignable(expectedComponent, component); + verifyAssignable(Type.INTEGER, index); + simplePush(component, frame); + } + + private void evalArrayStore(Type expectedComponent, Frame frame) throws BadBytecode { + Type value = simplePop(frame); + Type index = frame.pop(); + Type array = frame.pop(); + + if (array == Type.UNINIT) { + verifyAssignable(Type.INTEGER, index); + return; + } + + Type component = array.getComponent(); + + if (component == null) + throw new BadBytecode("Not an array! [pos = " + lastPos + "]: " + component); + + component = zeroExtend(component); + + verifyAssignable(expectedComponent, component); + verifyAssignable(Type.INTEGER, index); + + // This intentionally only checks for Object on aastore + // downconverting of an array (no casts) + // e.g. Object[] blah = new String[]; + // blah[2] = (Object) "test"; + // blah[3] = new Integer(); // compiler doesnt catch it (has legal bytecode), + // // but will throw arraystoreexception + if (expectedComponent == Type.OBJECT) { + verifyAssignable(expectedComponent, value); + } else { + verifyAssignable(component, value); + } + } + + private void evalBinaryMath(Type expected, Frame frame) throws BadBytecode { + Type value2 = simplePop(frame); + Type value1 = simplePop(frame); + + verifyAssignable(expected, value2); + verifyAssignable(expected, value1); + simplePush(value1, frame); + } + + private void evalGetField(int opcode, int index, Frame frame) throws BadBytecode { + String desc = constPool.getFieldrefType(index); + Type type = zeroExtend(typeFromDesc(desc)); + + if (opcode == GETFIELD) { + Type objectType = resolveClassInfo(constPool.getFieldrefClassName(index)); + verifyAssignable(objectType, simplePop(frame)); + } + + simplePush(type, frame); + } + + private void evalInvokeIntfMethod(int opcode, int index, Frame frame) throws BadBytecode { + String desc = constPool.getInterfaceMethodrefType(index); + Type[] types = paramTypesFromDesc(desc); + int i = types.length; + + while (i > 0) + verifyAssignable(zeroExtend(types[--i]), simplePop(frame)); + + String classInfo = constPool.getInterfaceMethodrefClassName(index); + Type objectType = resolveClassInfo(classInfo); + verifyAssignable(objectType, simplePop(frame)); + + Type returnType = returnTypeFromDesc(desc); + if (returnType != Type.VOID) + simplePush(zeroExtend(returnType), frame); + } + + private void evalInvokeMethod(int opcode, int index, Frame frame) throws BadBytecode { + String desc = constPool.getMethodrefType(index); + Type[] types = paramTypesFromDesc(desc); + int i = types.length; + + while (i > 0) + verifyAssignable(zeroExtend(types[--i]), simplePop(frame)); + + if (opcode != INVOKESTATIC) { + Type objectType = resolveClassInfo(constPool.getMethodrefClassName(index)); + verifyAssignable(objectType, simplePop(frame)); + } + + Type returnType = returnTypeFromDesc(desc); + if (returnType != Type.VOID) + simplePush(zeroExtend(returnType), frame); + } + + + private void evalLDC(int index, Frame frame) throws BadBytecode { + int tag = constPool.getTag(index); + Type type; + switch (tag) { + case ConstPool.CONST_String: + type = STRING_TYPE; + break; + case ConstPool.CONST_Integer: + type = Type.INTEGER; + break; + case ConstPool.CONST_Float: + type = Type.FLOAT; + break; + case ConstPool.CONST_Long: + type = Type.LONG; + break; + case ConstPool.CONST_Double: + type = Type.DOUBLE; + break; + case ConstPool.CONST_Class: + type = CLASS_TYPE; + break; + default: + throw new BadBytecode("bad LDC [pos = " + lastPos + "]: " + tag); + } + + simplePush(type, frame); + } + + private void evalLoad(Type expected, int index, Frame frame, Subroutine subroutine) throws BadBytecode { + Type type = frame.getLocal(index); + + verifyAssignable(expected, type); + + simplePush(type, frame); + access(index, type, subroutine); + } + + private void evalNewArray(int pos, CodeIterator iter, Frame frame) throws BadBytecode { + verifyAssignable(Type.INTEGER, simplePop(frame)); + Type type = null; + int typeInfo = iter.byteAt(pos + 1); + switch (typeInfo) { + case T_BOOLEAN: + type = getType("boolean[]"); + break; + case T_CHAR: + type = getType("char[]"); + break; + case T_BYTE: + type = getType("byte[]"); + break; + case T_SHORT: + type = getType("short[]"); + break; + case T_INT: + type = getType("int[]"); + break; + case T_LONG: + type = getType("long[]"); + break; + case T_FLOAT: + type = getType("float[]"); + break; + case T_DOUBLE: + type = getType("double[]"); + break; + default: + throw new BadBytecode("Invalid array type [pos = " + pos + "]: " + typeInfo); + + } + + frame.push(type); + } + + private void evalNewObjectArray(int pos, CodeIterator iter, Frame frame) throws BadBytecode { + // Convert to x[] format + Type type = resolveClassInfo(constPool.getClassInfo(iter.u16bitAt(pos + 1))); + String name = type.getCtClass().getName(); + int opcode = iter.byteAt(pos); + int dimensions; + + if (opcode == MULTIANEWARRAY) { + dimensions = iter.byteAt(pos + 3); + } else { + name = name + "[]"; + dimensions = 1; + } + + while (dimensions-- > 0) { + verifyAssignable(Type.INTEGER, simplePop(frame)); + } + + simplePush(getType(name), frame); + } + + private void evalPutField(int opcode, int index, Frame frame) throws BadBytecode { + String desc = constPool.getFieldrefType(index); + Type type = zeroExtend(typeFromDesc(desc)); + + verifyAssignable(type, simplePop(frame)); + + if (opcode == PUTFIELD) { + Type objectType = resolveClassInfo(constPool.getFieldrefClassName(index)); + verifyAssignable(objectType, simplePop(frame)); + } + } + + private void evalShift(Type expected, Frame frame) throws BadBytecode { + Type value2 = simplePop(frame); + Type value1 = simplePop(frame); + + verifyAssignable(Type.INTEGER, value2); + verifyAssignable(expected, value1); + simplePush(value1, frame); + } + + private void evalStore(Type expected, int index, Frame frame, Subroutine subroutine) throws BadBytecode { + Type type = simplePop(frame); + + // RETURN_ADDRESS is allowed by ASTORE + if (! (expected == Type.OBJECT && type == Type.RETURN_ADDRESS)) + verifyAssignable(expected, type); + simpleSetLocal(index, type, frame); + access(index, type, subroutine); + } + + private void evalWide(int pos, CodeIterator iter, Frame frame, Subroutine subroutine) throws BadBytecode { + int opcode = iter.byteAt(pos + 1); + int index = iter.u16bitAt(pos + 2); + switch (opcode) { + case ILOAD: + evalLoad(Type.INTEGER, index, frame, subroutine); + break; + case LLOAD: + evalLoad(Type.LONG, index, frame, subroutine); + break; + case FLOAD: + evalLoad(Type.FLOAT, index, frame, subroutine); + break; + case DLOAD: + evalLoad(Type.DOUBLE, index, frame, subroutine); + break; + case ALOAD: + evalLoad(Type.OBJECT, index, frame, subroutine); + break; + case ISTORE: + evalStore(Type.INTEGER, index, frame, subroutine); + break; + case LSTORE: + evalStore(Type.LONG, index, frame, subroutine); + break; + case FSTORE: + evalStore(Type.FLOAT, index, frame, subroutine); + break; + case DSTORE: + evalStore(Type.DOUBLE, index, frame, subroutine); + break; + case ASTORE: + evalStore(Type.OBJECT, index, frame, subroutine); + break; + case IINC: + verifyAssignable(Type.INTEGER, frame.getLocal(index)); + break; + case RET: + verifyAssignable(Type.RETURN_ADDRESS, frame.getLocal(index)); + break; + default: + throw new BadBytecode("Invalid WIDE operand [pos = " + pos + "]: " + opcode); + } + + } + + private Type getType(String name) throws BadBytecode { + try { + return Type.get(classPool.get(name)); + } catch (NotFoundException e) { + throw new BadBytecode("Could not find class [pos = " + lastPos + "]: " + name); + } + } + + private Type[] paramTypesFromDesc(String desc) throws BadBytecode { + CtClass classes[] = null; + try { + classes = Descriptor.getParameterTypes(desc, classPool); + } catch (NotFoundException e) { + throw new BadBytecode("Could not find class in descriptor [pos = " + lastPos + "]: " + e.getMessage()); + } + + if (classes == null) + throw new BadBytecode("Could not obtain parameters for descriptor [pos = " + lastPos + "]: " + desc); + + Type[] types = new Type[classes.length]; + for (int i = 0; i < types.length; i++) + types[i] = Type.get(classes[i]); + + return types; + } + + private Type returnTypeFromDesc(String desc) throws BadBytecode { + CtClass clazz = null; + try { + clazz = Descriptor.getReturnType(desc, classPool); + } catch (NotFoundException e) { + throw new BadBytecode("Could not find class in descriptor [pos = " + lastPos + "]: " + e.getMessage()); + } + + if (clazz == null) + throw new BadBytecode("Could not obtain return type for descriptor [pos = " + lastPos + "]: " + desc); + + return Type.get(clazz); + } + + private Type simplePeek(Frame frame) { + Type type = frame.peek(); + return (type == Type.TOP) ? frame.getStack(frame.getTopIndex() - 1) : type; + } + + private Type simplePop(Frame frame) { + Type type = frame.pop(); + return (type == Type.TOP) ? frame.pop() : type; + } + + private void simplePush(Type type, Frame frame) { + frame.push(type); + if (type.getSize() == 2) + frame.push(Type.TOP); + } + + private void access(int index, Type type, Subroutine subroutine) { + if (subroutine == null) + return; + subroutine.access(index); + if (type.getSize() == 2) + subroutine.access(index + 1); + } + + private void simpleSetLocal(int index, Type type, Frame frame) { + frame.setLocal(index, type); + if (type.getSize() == 2) + frame.setLocal(index + 1, Type.TOP); + } + + private Type resolveClassInfo(String info) throws BadBytecode { + CtClass clazz = null; + try { + if (info.charAt(0) == '[') { + clazz = Descriptor.toCtClass(info, classPool); + } else { + clazz = classPool.get(info); + } + + } catch (NotFoundException e) { + throw new BadBytecode("Could not find class in descriptor [pos = " + lastPos + "]: " + e.getMessage()); + } + + if (clazz == null) + throw new BadBytecode("Could not obtain type for descriptor [pos = " + lastPos + "]: " + info); + + return Type.get(clazz); + } + + private Type typeFromDesc(String desc) throws BadBytecode { + CtClass clazz = null; + try { + clazz = Descriptor.toCtClass(desc, classPool); + } catch (NotFoundException e) { + throw new BadBytecode("Could not find class in descriptor [pos = " + lastPos + "]: " + e.getMessage()); + } + + if (clazz == null) + throw new BadBytecode("Could not obtain type for descriptor [pos = " + lastPos + "]: " + desc); + + return Type.get(clazz); + } + + private void verifyAssignable(Type expected, Type type) throws BadBytecode { + if (! expected.isAssignableFrom(type)) + throw new BadBytecode("Expected type: " + expected + " Got: " + type + " [pos = " + lastPos + "]"); + } +} diff --git a/src/main/javassist/bytecode/analysis/Frame.java b/src/main/javassist/bytecode/analysis/Frame.java new file mode 100644 index 0000000..cf646f4 --- /dev/null +++ b/src/main/javassist/bytecode/analysis/Frame.java @@ -0,0 +1,288 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba, and others. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ +package javassist.bytecode.analysis; + + +/** + * Represents the stack frame and local variable table at a particular point in time. + * + * @author Jason T. Greene + */ +public class Frame { + private Type[] locals; + private Type[] stack; + private int top; + private boolean jsrMerged; + private boolean retMerged; + + /** + * Create a new frame with the specified local variable table size, and max stack size + * + * @param locals the number of local variable table entries + * @param stack the maximum stack size + */ + public Frame(int locals, int stack) { + this.locals = new Type[locals]; + this.stack = new Type[stack]; + } + + /** + * Returns the local varaible table entry at index. + * + * @param index the position in the table + * @return the type if one exists, or null if the position is empty + */ + public Type getLocal(int index) { + return locals[index]; + } + + /** + * Sets the local variable table entry at index to a type. + * + * @param index the position in the table + * @param type the type to set at the position + */ + public void setLocal(int index, Type type) { + locals[index] = type; + } + + + /** + * Returns the type on the stack at the specified index. + * + * @param index the position on the stack + * @return the type of the stack position + */ + public Type getStack(int index) { + return stack[index]; + } + + /** + * Sets the type of the stack position + * + * @param index the position on the stack + * @param type the type to set + */ + public void setStack(int index, Type type) { + stack[index] = type; + } + + /** + * Empties the stack + */ + public void clearStack() { + top = 0; + } + + /** + * Gets the index of the type sitting at the top of the stack. + * This is not to be confused with a length operation which + * would return the number of elements, not the position of + * the last element. + * + * @return the position of the element at the top of the stack + */ + public int getTopIndex() { + return top - 1; + } + + /** + * Returns the number of local variable table entries, specified + * at construction. + * + * @return the number of local variable table entries + */ + public int localsLength() { + return locals.length; + } + + /** + * Gets the top of the stack without altering it + * + * @return the top of the stack + */ + public Type peek() { + if (top < 1) + throw new IndexOutOfBoundsException("Stack is empty"); + + return stack[top - 1]; + } + + /** + * Alters the stack to contain one less element and return it. + * + * @return the element popped from the stack + */ + public Type pop() { + if (top < 1) + throw new IndexOutOfBoundsException("Stack is empty"); + return stack[--top]; + } + + /** + * Alters the stack by placing the passed type on the top + * + * @param type the type to add to the top + */ + public void push(Type type) { + stack[top++] = type; + } + + + /** + * Makes a shallow copy of this frame, i.e. the type instances will + * remain the same. + * + * @return the shallow copy + */ + public Frame copy() { + Frame frame = new Frame(locals.length, stack.length); + System.arraycopy(locals, 0, frame.locals, 0, locals.length); + System.arraycopy(stack, 0, frame.stack, 0, stack.length); + frame.top = top; + return frame; + } + + /** + * Makes a shallow copy of the stack portion of this frame. The local + * variable table size will be copied, but its contents will be empty. + * + * @return the shallow copy of the stack + */ + public Frame copyStack() { + Frame frame = new Frame(locals.length, stack.length); + System.arraycopy(stack, 0, frame.stack, 0, stack.length); + frame.top = top; + return frame; + } + + /** + * Merges all types on the stack of this frame instance with that of the specified frame. + * The local variable table is left untouched. + * + * @param frame the frame to merge the stack from + * @return true if any changes where made + */ + public boolean mergeStack(Frame frame) { + boolean changed = false; + if (top != frame.top) + throw new RuntimeException("Operand stacks could not be merged, they are different sizes!"); + + for (int i = 0; i < top; i++) { + if (stack[i] != null) { + Type prev = stack[i]; + Type merged = prev.merge(frame.stack[i]); + if (merged == Type.BOGUS) + throw new RuntimeException("Operand stacks could not be merged due to differing primitive types: pos = " + i); + + stack[i] = merged; + // always replace the instance in case a multi-interface type changes to a normal Type + if ((! merged.equals(prev)) || merged.popChanged()) { + changed = true; + } + } + } + + return changed; + } + + /** + * Merges all types on the stack and local variable table of this frame with that of the specified + * type. + * + * @param frame the frame to merge with + * @return true if any changes to this frame where made by this merge + */ + public boolean merge(Frame frame) { + boolean changed = false; + + // Local variable table + for (int i = 0; i < locals.length; i++) { + if (locals[i] != null) { + Type prev = locals[i]; + Type merged = prev.merge(frame.locals[i]); + // always replace the instance in case a multi-interface type changes to a normal Type + locals[i] = merged; + if (! merged.equals(prev) || merged.popChanged()) { + changed = true; + } + } else if (frame.locals[i] != null) { + locals[i] = frame.locals[i]; + changed = true; + } + } + + changed |= mergeStack(frame); + return changed; + } + + public String toString() { + StringBuffer buffer = new StringBuffer(); + + buffer.append("locals = ["); + for (int i = 0; i < locals.length; i++) { + buffer.append(locals[i] == null ? "empty" : locals[i].toString()); + if (i < locals.length - 1) + buffer.append(", "); + } + buffer.append("] stack = ["); + for (int i = 0; i < top; i++) { + buffer.append(stack[i]); + if (i < top - 1) + buffer.append(", "); + } + buffer.append("]"); + + return buffer.toString(); + } + + /** + * Whether or not state from the source JSR instruction has been merged + * + * @return true if JSR state has been merged + */ + boolean isJsrMerged() { + return jsrMerged; + } + + /** + * Sets whether of not the state from the source JSR instruction has been merged + * + * @param jsrMerged true if merged, otherwise false + */ + void setJsrMerged(boolean jsrMerged) { + this.jsrMerged = jsrMerged; + } + + /** + * Whether or not state from the RET instruction, of the subroutine that was jumped + * to has been merged. + * + * @return true if RET state has been merged + */ + boolean isRetMerged() { + return retMerged; + } + + /** + * Sets whether or not state from the RET instruction, of the subroutine that was jumped + * to has been merged. + * + * @param retMerged true if RET state has been merged + */ + void setRetMerged(boolean retMerged) { + this.retMerged = retMerged; + } +} diff --git a/src/main/javassist/bytecode/analysis/FramePrinter.java b/src/main/javassist/bytecode/analysis/FramePrinter.java new file mode 100644 index 0000000..fc99cd3 --- /dev/null +++ b/src/main/javassist/bytecode/analysis/FramePrinter.java @@ -0,0 +1,147 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba, and others. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ +package javassist.bytecode.analysis; + +import java.io.PrintStream; + +import javassist.CtClass; +import javassist.CtMethod; +import javassist.Modifier; +import javassist.NotFoundException; +import javassist.bytecode.BadBytecode; +import javassist.bytecode.CodeAttribute; +import javassist.bytecode.CodeIterator; +import javassist.bytecode.ConstPool; +import javassist.bytecode.Descriptor; +import javassist.bytecode.InstructionPrinter; +import javassist.bytecode.MethodInfo; + +/** + * A utility class for printing a merged view of the frame state and the + * instructions of a method. + * + * @author Jason T. Greene + */ +public final class FramePrinter { + private final PrintStream stream; + + /** + * Constructs a bytecode printer. + */ + public FramePrinter(PrintStream stream) { + this.stream = stream; + } + + /** + * Prints all the methods declared in the given class. + */ + public static void print(CtClass clazz, PrintStream stream) { + (new FramePrinter(stream)).print(clazz); + } + + /** + * Prints all the methods declared in the given class. + */ + public void print(CtClass clazz) { + CtMethod[] methods = clazz.getDeclaredMethods(); + for (int i = 0; i < methods.length; i++) { + print(methods[i]); + } + } + + private String getMethodString(CtMethod method) { + try { + return Modifier.toString(method.getModifiers()) + " " + + method.getReturnType().getName() + " " + method.getName() + + Descriptor.toString(method.getSignature()) + ";"; + } catch (NotFoundException e) { + throw new RuntimeException(e); + } + } + + /** + * Prints the instructions and the frame states of the given method. + */ + public void print(CtMethod method) { + stream.println("\n" + getMethodString(method)); + MethodInfo info = method.getMethodInfo2(); + ConstPool pool = info.getConstPool(); + CodeAttribute code = info.getCodeAttribute(); + if (code == null) + return; + + Frame[] frames; + try { + frames = (new Analyzer()).analyze(method.getDeclaringClass(), info); + } catch (BadBytecode e) { + throw new RuntimeException(e); + } + + int spacing = String.valueOf(code.getCodeLength()).length(); + + CodeIterator iterator = code.iterator(); + while (iterator.hasNext()) { + int pos; + try { + pos = iterator.next(); + } catch (BadBytecode e) { + throw new RuntimeException(e); + } + + stream.println(pos + ": " + InstructionPrinter.instructionString(iterator, pos, pool)); + + addSpacing(spacing + 3); + Frame frame = frames[pos]; + if (frame == null) { + stream.println("--DEAD CODE--"); + continue; + } + printStack(frame); + + addSpacing(spacing + 3); + printLocals(frame); + } + + } + + private void printStack(Frame frame) { + stream.print("stack ["); + int top = frame.getTopIndex(); + for (int i = 0; i <= top; i++) { + if (i > 0) + stream.print(", "); + Type type = frame.getStack(i); + stream.print(type); + } + stream.println("]"); + } + + private void printLocals(Frame frame) { + stream.print("locals ["); + int length = frame.localsLength(); + for (int i = 0; i < length; i++) { + if (i > 0) + stream.print(", "); + Type type = frame.getLocal(i); + stream.print(type == null ? "empty" : type.toString()); + } + stream.println("]"); + } + + private void addSpacing(int count) { + while (count-- > 0) + stream.print(' '); + } +} diff --git a/src/main/javassist/bytecode/analysis/IntQueue.java b/src/main/javassist/bytecode/analysis/IntQueue.java new file mode 100644 index 0000000..d50cddc --- /dev/null +++ b/src/main/javassist/bytecode/analysis/IntQueue.java @@ -0,0 +1,56 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba, and others. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ +package javassist.bytecode.analysis; + +import java.util.NoSuchElementException; + +class IntQueue { + private static class Entry { + private IntQueue.Entry next; + private int value; + private Entry(int value) { + this.value = value; + } + } + private IntQueue.Entry head; + + private IntQueue.Entry tail; + + void add(int value) { + IntQueue.Entry entry = new Entry(value); + if (tail != null) + tail.next = entry; + tail = entry; + + if (head == null) + head = entry; + } + + boolean isEmpty() { + return head == null; + } + + int take() { + if (head == null) + throw new NoSuchElementException(); + + int value = head.value; + head = head.next; + if (head == null) + tail = null; + + return value; + } +}
\ No newline at end of file diff --git a/src/main/javassist/bytecode/analysis/MultiArrayType.java b/src/main/javassist/bytecode/analysis/MultiArrayType.java new file mode 100644 index 0000000..750116c --- /dev/null +++ b/src/main/javassist/bytecode/analysis/MultiArrayType.java @@ -0,0 +1,129 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba, and others. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ +package javassist.bytecode.analysis; + +import javassist.ClassPool; +import javassist.CtClass; +import javassist.NotFoundException; + +/** + * Represents an array of {@link MultiType} instances. + * + * @author Jason T. Greene + */ +public class MultiArrayType extends Type { + private MultiType component; + private int dims; + + public MultiArrayType(MultiType component, int dims) { + super(null); + this.component = component; + this.dims = dims; + } + + public CtClass getCtClass() { + CtClass clazz = component.getCtClass(); + if (clazz == null) + return null; + + ClassPool pool = clazz.getClassPool(); + if (pool == null) + pool = ClassPool.getDefault(); + + String name = arrayName(clazz.getName(), dims); + + try { + return pool.get(name); + } catch (NotFoundException e) { + throw new RuntimeException(e); + } + } + + boolean popChanged() { + return component.popChanged(); + } + + public int getDimensions() { + return dims; + } + + public Type getComponent() { + return dims == 1 ? (Type)component : new MultiArrayType(component, dims - 1); + } + + public int getSize() { + return 1; + } + + public boolean isArray() { + return true; + } + + public boolean isAssignableFrom(Type type) { + throw new UnsupportedOperationException("Not implemented"); + } + + public boolean isReference() { + return true; + } + + public boolean isAssignableTo(Type type) { + if (eq(type.getCtClass(), Type.OBJECT.getCtClass())) + return true; + + if (eq(type.getCtClass(), Type.CLONEABLE.getCtClass())) + return true; + + if (eq(type.getCtClass(), Type.SERIALIZABLE.getCtClass())) + return true; + + if (! type.isArray()) + return false; + + Type typeRoot = getRootComponent(type); + int typeDims = type.getDimensions(); + + if (typeDims > dims) + return false; + + if (typeDims < dims) { + if (eq(typeRoot.getCtClass(), Type.OBJECT.getCtClass())) + return true; + + if (eq(typeRoot.getCtClass(), Type.CLONEABLE.getCtClass())) + return true; + + if (eq(typeRoot.getCtClass(), Type.SERIALIZABLE.getCtClass())) + return true; + + return false; + } + + return component.isAssignableTo(typeRoot); + } + + public boolean equals(Object o) { + if (! (o instanceof MultiArrayType)) + return false; + MultiArrayType multi = (MultiArrayType)o; + + return component.equals(multi.component) && dims == multi.dims; + } + + public String toString() { + // follows the same detailed formating scheme as component + return arrayName(component.toString(), dims); + } +} diff --git a/src/main/javassist/bytecode/analysis/MultiType.java b/src/main/javassist/bytecode/analysis/MultiType.java new file mode 100644 index 0000000..3fb1488 --- /dev/null +++ b/src/main/javassist/bytecode/analysis/MultiType.java @@ -0,0 +1,313 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba, and others. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ +package javassist.bytecode.analysis; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import javassist.CtClass; + +/** + * MultiType represents an unresolved type. Whenever two <literal>Type</literal> + * instances are merged, if they share more than one super type (either an + * interface or a superclass), then a <literal>MultiType</literal> is used to + * represent the possible super types. The goal of a <literal>MultiType</literal> + * is to reduce the set of possible types down to a single resolved type. This + * is done by eliminating non-assignable types from the typeset when the + * <literal>MultiType</literal> is passed as an argument to + * {@link Type#isAssignableFrom(Type)}, as well as removing non-intersecting + * types during a merge. + * + * Note: Currently the <litera>MultiType</literal> instance is reused as much + * as possible so that updates are visible from all frames. In addition, all + * <literal>MultiType</literal> merge paths are also updated. This is somewhat + * hackish, but it appears to handle most scenarios. + * + * @author Jason T. Greene + */ + +/* TODO - A better, but more involved, approach would be to track the instruction + * offset that resulted in the creation of this type, and + * whenever the typeset changes, to force a merge on that position. This + * would require creating a new MultiType instance every time the typeset + * changes, and somehow communicating assignment changes to the Analyzer + */ +public class MultiType extends Type { + private Map interfaces; + private Type resolved; + private Type potentialClass; + private MultiType mergeSource; + private boolean changed = false; + + public MultiType(Map interfaces) { + this(interfaces, null); + } + + public MultiType(Map interfaces, Type potentialClass) { + super(null); + this.interfaces = interfaces; + this.potentialClass = potentialClass; + } + + /** + * Gets the class that corresponds with this type. If this information + * is not yet known, java.lang.Object will be returned. + */ + public CtClass getCtClass() { + if (resolved != null) + return resolved.getCtClass(); + + return Type.OBJECT.getCtClass(); + } + + /** + * Always returns null since this type is never used for an array. + */ + public Type getComponent() { + return null; + } + + /** + * Always returns 1, since this type is a reference. + */ + public int getSize() { + return 1; + } + + /** + * Always reutnrs false since this type is never used for an array + */ + public boolean isArray() { + return false; + } + + /** + * Returns true if the internal state has changed. + */ + boolean popChanged() { + boolean changed = this.changed; + this.changed = false; + return changed; + } + + public boolean isAssignableFrom(Type type) { + throw new UnsupportedOperationException("Not implemented"); + } + + public boolean isAssignableTo(Type type) { + if (resolved != null) + return type.isAssignableFrom(resolved); + + if (Type.OBJECT.equals(type)) + return true; + + if (potentialClass != null && !type.isAssignableFrom(potentialClass)) + potentialClass = null; + + Map map = mergeMultiAndSingle(this, type); + + if (map.size() == 1 && potentialClass == null) { + // Update previous merge paths to the same resolved type + resolved = Type.get((CtClass)map.values().iterator().next()); + propogateResolved(); + + return true; + } + + // Keep all previous merge paths up to date + if (map.size() >= 1) { + interfaces = map; + propogateState(); + + return true; + } + + if (potentialClass != null) { + resolved = potentialClass; + propogateResolved(); + + return true; + } + + return false; + } + + private void propogateState() { + MultiType source = mergeSource; + while (source != null) { + source.interfaces = interfaces; + source.potentialClass = potentialClass; + source = source.mergeSource; + } + } + + private void propogateResolved() { + MultiType source = mergeSource; + while (source != null) { + source.resolved = resolved; + source = source.mergeSource; + } + } + + /** + * Always returns true, since this type is always a reference. + * + * @return true + */ + public boolean isReference() { + return true; + } + + private Map getAllMultiInterfaces(MultiType type) { + Map map = new HashMap(); + + Iterator iter = type.interfaces.values().iterator(); + while (iter.hasNext()) { + CtClass intf = (CtClass)iter.next(); + map.put(intf.getName(), intf); + getAllInterfaces(intf, map); + } + + return map; + } + + + private Map mergeMultiInterfaces(MultiType type1, MultiType type2) { + Map map1 = getAllMultiInterfaces(type1); + Map map2 = getAllMultiInterfaces(type2); + + return findCommonInterfaces(map1, map2); + } + + private Map mergeMultiAndSingle(MultiType multi, Type single) { + Map map1 = getAllMultiInterfaces(multi); + Map map2 = getAllInterfaces(single.getCtClass(), null); + + return findCommonInterfaces(map1, map2); + } + + private boolean inMergeSource(MultiType source) { + while (source != null) { + if (source == this) + return true; + + source = source.mergeSource; + } + + return false; + } + + public Type merge(Type type) { + if (this == type) + return this; + + if (type == UNINIT) + return this; + + if (type == BOGUS) + return BOGUS; + + if (type == null) + return this; + + if (resolved != null) + return resolved.merge(type); + + if (potentialClass != null) { + Type mergePotential = potentialClass.merge(type); + if (! mergePotential.equals(potentialClass) || mergePotential.popChanged()) { + potentialClass = Type.OBJECT.equals(mergePotential) ? null : mergePotential; + changed = true; + } + } + + Map merged; + + if (type instanceof MultiType) { + MultiType multi = (MultiType)type; + + if (multi.resolved != null) { + merged = mergeMultiAndSingle(this, multi.resolved); + } else { + merged = mergeMultiInterfaces(multi, this); + if (! inMergeSource(multi)) + mergeSource = multi; + } + } else { + merged = mergeMultiAndSingle(this, type); + } + + // Keep all previous merge paths up to date + if (merged.size() > 1 || (merged.size() == 1 && potentialClass != null)) { + // Check for changes + if (merged.size() != interfaces.size()) { + changed = true; + } else if (changed == false){ + Iterator iter = merged.keySet().iterator(); + while (iter.hasNext()) + if (! interfaces.containsKey(iter.next())) + changed = true; + } + + interfaces = merged; + propogateState(); + + return this; + } + + if (merged.size() == 1) { + resolved = Type.get((CtClass) merged.values().iterator().next()); + } else if (potentialClass != null){ + resolved = potentialClass; + } else { + resolved = OBJECT; + } + + propogateResolved(); + + return resolved; + } + + public boolean equals(Object o) { + if (! (o instanceof MultiType)) + return false; + + MultiType multi = (MultiType) o; + if (resolved != null) + return resolved.equals(multi.resolved); + else if (multi.resolved != null) + return false; + + return interfaces.keySet().equals(multi.interfaces.keySet()); + } + + public String toString() { + if (resolved != null) + return resolved.toString(); + + StringBuffer buffer = new StringBuffer("{"); + Iterator iter = interfaces.keySet().iterator(); + while (iter.hasNext()) { + buffer.append(iter.next()); + buffer.append(", "); + } + buffer.setLength(buffer.length() - 2); + if (potentialClass != null) + buffer.append(", *").append(potentialClass.toString()); + buffer.append("}"); + return buffer.toString(); + } +} diff --git a/src/main/javassist/bytecode/analysis/Subroutine.java b/src/main/javassist/bytecode/analysis/Subroutine.java new file mode 100644 index 0000000..1347813 --- /dev/null +++ b/src/main/javassist/bytecode/analysis/Subroutine.java @@ -0,0 +1,66 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba, and others. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ +package javassist.bytecode.analysis; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * Represents a nested method subroutine (marked by JSR and RET). + * + * @author Jason T. Greene + */ +public class Subroutine { + //private Set callers = new HashSet(); + private List callers = new ArrayList(); + private Set access = new HashSet(); + private int start; + + public Subroutine(int start, int caller) { + this.start = start; + callers.add(new Integer(caller)); + } + + public void addCaller(int caller) { + callers.add(new Integer(caller)); + } + + public int start() { + return start; + } + + public void access(int index) { + access.add(new Integer(index)); + } + + public boolean isAccessed(int index) { + return access.contains(new Integer(index)); + } + + public Collection accessed() { + return access; + } + + public Collection callers() { + return callers; + } + + public String toString() { + return "start = " + start + " callers = " + callers.toString(); + } +}
\ No newline at end of file diff --git a/src/main/javassist/bytecode/analysis/SubroutineScanner.java b/src/main/javassist/bytecode/analysis/SubroutineScanner.java new file mode 100644 index 0000000..f42202e --- /dev/null +++ b/src/main/javassist/bytecode/analysis/SubroutineScanner.java @@ -0,0 +1,156 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba, and others. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ +package javassist.bytecode.analysis; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import javassist.bytecode.BadBytecode; +import javassist.bytecode.CodeAttribute; +import javassist.bytecode.CodeIterator; +import javassist.bytecode.ExceptionTable; +import javassist.bytecode.MethodInfo; +import javassist.bytecode.Opcode; + +/** + * Discovers the subroutines in a method, and tracks all callers. + * + * @author Jason T. Greene + */ +public class SubroutineScanner implements Opcode { + + private Subroutine[] subroutines; + Map subTable = new HashMap(); + Set done = new HashSet(); + + + public Subroutine[] scan(MethodInfo method) throws BadBytecode { + CodeAttribute code = method.getCodeAttribute(); + CodeIterator iter = code.iterator(); + + subroutines = new Subroutine[code.getCodeLength()]; + subTable.clear(); + done.clear(); + + scan(0, iter, null); + + ExceptionTable exceptions = code.getExceptionTable(); + for (int i = 0; i < exceptions.size(); i++) { + int handler = exceptions.handlerPc(i); + // If an exception is thrown in subroutine, the handler + // is part of the same subroutine. + scan(handler, iter, subroutines[exceptions.startPc(i)]); + } + + return subroutines; + } + + private void scan(int pos, CodeIterator iter, Subroutine sub) throws BadBytecode { + // Skip already processed blocks + if (done.contains(new Integer(pos))) + return; + + done.add(new Integer(pos)); + + int old = iter.lookAhead(); + iter.move(pos); + + boolean next; + do { + pos = iter.next(); + next = scanOp(pos, iter, sub) && iter.hasNext(); + } while (next); + + iter.move(old); + } + + private boolean scanOp(int pos, CodeIterator iter, Subroutine sub) throws BadBytecode { + subroutines[pos] = sub; + + int opcode = iter.byteAt(pos); + + if (opcode == TABLESWITCH) { + scanTableSwitch(pos, iter, sub); + + return false; + } + + if (opcode == LOOKUPSWITCH) { + scanLookupSwitch(pos, iter, sub); + + return false; + } + + // All forms of return and throw end current code flow + if (Util.isReturn(opcode) || opcode == RET || opcode == ATHROW) + return false; + + if (Util.isJumpInstruction(opcode)) { + int target = Util.getJumpTarget(pos, iter); + if (opcode == JSR || opcode == JSR_W) { + Subroutine s = (Subroutine) subTable.get(new Integer(target)); + if (s == null) { + s = new Subroutine(target, pos); + subTable.put(new Integer(target), s); + scan(target, iter, s); + } else { + s.addCaller(pos); + } + } else { + scan(target, iter, sub); + + // GOTO ends current code flow + if (Util.isGoto(opcode)) + return false; + } + } + + return true; + } + + private void scanLookupSwitch(int pos, CodeIterator iter, Subroutine sub) throws BadBytecode { + int index = (pos & ~3) + 4; + // default + scan(pos + iter.s32bitAt(index), iter, sub); + int npairs = iter.s32bitAt(index += 4); + int end = npairs * 8 + (index += 4); + + // skip "match" + for (index += 4; index < end; index += 8) { + int target = iter.s32bitAt(index) + pos; + scan(target, iter, sub); + } + } + + private void scanTableSwitch(int pos, CodeIterator iter, Subroutine sub) throws BadBytecode { + // Skip 4 byte alignment padding + int index = (pos & ~3) + 4; + // default + scan(pos + iter.s32bitAt(index), iter, sub); + int low = iter.s32bitAt(index += 4); + int high = iter.s32bitAt(index += 4); + int end = (high - low + 1) * 4 + (index += 4); + + // Offset table + for (; index < end; index += 4) { + int target = iter.s32bitAt(index) + pos; + scan(target, iter, sub); + } + } + + +} diff --git a/src/main/javassist/bytecode/analysis/Type.java b/src/main/javassist/bytecode/analysis/Type.java new file mode 100644 index 0000000..234f050 --- /dev/null +++ b/src/main/javassist/bytecode/analysis/Type.java @@ -0,0 +1,592 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba, and others. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ +package javassist.bytecode.analysis; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.Iterator; +import java.util.Map; + +import javassist.ClassPool; +import javassist.CtClass; +import javassist.NotFoundException; + +/** + * Represents a JVM type in data-flow analysis. This abstraction is necessary since + * a JVM type not only includes all normal Java types, but also a few special types + * that are used by the JVM internally. See the static field types on this class for + * more info on these special types. + * + * All primitive and special types reuse the same instance, so identity comparison can + * be used when examining them. Normal java types must use {@link #equals(Object)} to + * compare type instances. + * + * In most cases, applications which consume this API, only need to call {@link #getCtClass()} + * to obtain the needed type information. + * + * @author Jason T. Greene + */ +public class Type { + private final CtClass clazz; + private final boolean special; + + private static final Map prims = new IdentityHashMap(); + /** Represents the double primitive type */ + public static final Type DOUBLE = new Type(CtClass.doubleType); + /** Represents the boolean primitive type */ + public static final Type BOOLEAN = new Type(CtClass.booleanType); + /** Represents the long primitive type */ + public static final Type LONG = new Type(CtClass.longType); + /** Represents the char primitive type */ + public static final Type CHAR = new Type(CtClass.charType); + /** Represents the byte primitive type */ + public static final Type BYTE = new Type(CtClass.byteType); + /** Represents the short primitive type */ + public static final Type SHORT = new Type(CtClass.shortType); + /** Represents the integer primitive type */ + public static final Type INTEGER = new Type(CtClass.intType); + /** Represents the float primitive type */ + public static final Type FLOAT = new Type(CtClass.floatType); + /** Represents the void primitive type */ + public static final Type VOID = new Type(CtClass.voidType); + + /** + * Represents an unknown, or null type. This occurs when aconst_null is used. + * It is important not to treat this type as java.lang.Object, since a null can + * be assigned to any reference type. The analyzer will replace these with + * an actual known type if it can be determined by a merged path with known type + * information. If this type is encountered on a frame then it is guaranteed to + * be null, and the type information is simply not available. Any attempts to + * infer the type, without further information from the compiler would be a guess. + */ + public static final Type UNINIT = new Type(null); + + /** + * Represents an internal JVM return address, which is used by the RET + * instruction to return to a JSR that invoked the subroutine. + */ + public static final Type RETURN_ADDRESS = new Type(null, true); + + /** A placeholder used by the analyzer for the second word position of a double-word type */ + public static final Type TOP = new Type(null, true); + + /** + * Represents a non-accessible value. Code cannot access the value this type + * represents. It occurs when bytecode reuses a local variable table + * position with non-mergable types. An example would be compiled code which + * uses the same position for a primitive type in one branch, and a reference type + * in another branch. + */ + public static final Type BOGUS = new Type(null, true); + + /** Represents the java.lang.Object reference type */ + public static final Type OBJECT = lookupType("java.lang.Object"); + /** Represents the java.io.Serializable reference type */ + public static final Type SERIALIZABLE = lookupType("java.io.Serializable"); + /** Represents the java.lang.Coneable reference type */ + public static final Type CLONEABLE = lookupType("java.lang.Cloneable"); + /** Represents the java.lang.Throwable reference type */ + public static final Type THROWABLE = lookupType("java.lang.Throwable"); + + static { + prims.put(CtClass.doubleType, DOUBLE); + prims.put(CtClass.longType, LONG); + prims.put(CtClass.charType, CHAR); + prims.put(CtClass.shortType, SHORT); + prims.put(CtClass.intType, INTEGER); + prims.put(CtClass.floatType, FLOAT); + prims.put(CtClass.byteType, BYTE); + prims.put(CtClass.booleanType, BOOLEAN); + prims.put(CtClass.voidType, VOID); + + } + + /** + * Obtain the Type for a given class. If the class is a primitive, + * the the unique type instance for the primitive will be returned. + * Otherwise a new Type instance representing the class is returned. + * + * @param clazz The java class + * @return a type instance for this class + */ + public static Type get(CtClass clazz) { + Type type = (Type)prims.get(clazz); + return type != null ? type : new Type(clazz); + } + + private static Type lookupType(String name) { + try { + return new Type(ClassPool.getDefault().get(name)); + } catch (NotFoundException e) { + throw new RuntimeException(e); + } + } + + Type(CtClass clazz) { + this(clazz, false); + } + + private Type(CtClass clazz, boolean special) { + this.clazz = clazz; + this.special = special; + } + + // Used to indicate a merge internally triggered a change + boolean popChanged() { + return false; + } + + /** + * Gets the word size of this type. Double-word types, such as long and double + * will occupy two positions on the local variable table or stack. + * + * @return the number of words needed to hold this type + */ + public int getSize() { + return clazz == CtClass.doubleType || clazz == CtClass.longType || this == TOP ? 2 : 1; + } + + /** + * Returns the class this type represents. If the type is special, null will be returned. + * + * @return the class for this type, or null if special + */ + public CtClass getCtClass() { + return clazz; + } + + /** + * Returns whether or not this type is a normal java reference, i.e. it is or extends java.lang.Object. + * + * @return true if a java reference, false if a primitive or special + */ + public boolean isReference() { + return !special && (clazz == null || !clazz.isPrimitive()); + } + + /** + * Returns whether or not the type is special. A special type is one that is either used + * for internal tracking, or is only used internally by the JVM. + * + * @return true if special, false if not + */ + public boolean isSpecial() { + return special; + } + + /** + * Returns whether or not this type is an array. + * + * @return true if an array, false if not + */ + public boolean isArray() { + return clazz != null && clazz.isArray(); + } + + /** + * Returns the number of dimensions of this array. If the type is not an + * array zero is returned. + * + * @return zero if not an array, otherwise the number of array dimensions. + */ + public int getDimensions() { + if (!isArray()) return 0; + + String name = clazz.getName(); + int pos = name.length() - 1; + int count = 0; + while (name.charAt(pos) == ']' ) { + pos -= 2; + count++; + } + + return count; + } + + /** + * Returns the array component if this type is an array. If the type + * is not an array null is returned. + * + * @return the array component if an array, otherwise null + */ + public Type getComponent() { + if (this.clazz == null || !this.clazz.isArray()) + return null; + + CtClass component; + try { + component = this.clazz.getComponentType(); + } catch (NotFoundException e) { + throw new RuntimeException(e); + } + + Type type = (Type)prims.get(component); + return (type != null) ? type : new Type(component); + } + + /** + * Determines whether this type is assignable, to the passed type. + * A type is assignable to another if it is either the same type, or + * a sub-type. + * + * @param type the type to test assignability to + * @return true if this is assignable to type, otherwise false + */ + public boolean isAssignableFrom(Type type) { + if (this == type) + return true; + + if ((type == UNINIT && isReference()) || this == UNINIT && type.isReference()) + return true; + + if (type instanceof MultiType) + return ((MultiType)type).isAssignableTo(this); + + if (type instanceof MultiArrayType) + return ((MultiArrayType)type).isAssignableTo(this); + + + // Primitives and Special types must be identical + if (clazz == null || clazz.isPrimitive()) + return false; + + try { + return type.clazz.subtypeOf(clazz); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * Finds the common base type, or interface which both this and the specified + * type can be assigned. If there is more than one possible answer, then a {@link MultiType}, + * or a {@link MultiArrayType} is returned. Multi-types have special rules, + * and successive merges and assignment tests on them will alter their internal state, + * as well as other multi-types they have been merged with. This method is used by + * the data-flow analyzer to merge the type state from multiple branches. + * + * @param type the type to merge with + * @return the merged type + */ + public Type merge(Type type) { + if (type == this) + return this; + if (type == null) + return this; + if (type == Type.UNINIT) + return this; + if (this == Type.UNINIT) + return type; + + // Unequal primitives and special types can not be merged + if (! type.isReference() || ! this.isReference()) + return BOGUS; + + // Centralize merging of multi-interface types + if (type instanceof MultiType) + return type.merge(this); + + if (type.isArray() && this.isArray()) + return mergeArray(type); + + try { + return mergeClasses(type); + } catch (NotFoundException e) { + throw new RuntimeException(e); + } + } + + Type getRootComponent(Type type) { + while (type.isArray()) + type = type.getComponent(); + + return type; + } + + private Type createArray(Type rootComponent, int dims) { + if (rootComponent instanceof MultiType) + return new MultiArrayType((MultiType) rootComponent, dims); + + String name = arrayName(rootComponent.clazz.getName(), dims); + + Type type; + try { + type = Type.get(getClassPool(rootComponent).get(name)); + } catch (NotFoundException e) { + throw new RuntimeException(e); + } + + return type; + } + + String arrayName(String component, int dims) { + // Using char[] since we have no StringBuilder in JDK4, and StringBuffer is slow. + // Although, this is more efficient even if we did have one. + int i = component.length(); + int size = i + dims * 2; + char[] string = new char[size]; + component.getChars(0, i, string, 0); + while (i < size) { + string[i++] = '['; + string[i++] = ']'; + } + component = new String(string); + return component; + } + + private ClassPool getClassPool(Type rootComponent) { + ClassPool pool = rootComponent.clazz.getClassPool(); + return pool != null ? pool : ClassPool.getDefault(); + } + + private Type mergeArray(Type type) { + Type typeRoot = getRootComponent(type); + Type thisRoot = getRootComponent(this); + int typeDims = type.getDimensions(); + int thisDims = this.getDimensions(); + + // Array commponents can be merged when the dimensions are equal + if (typeDims == thisDims) { + Type mergedComponent = thisRoot.merge(typeRoot); + + // If the components can not be merged (a primitive component mixed with a different type) + // then Object is the common type. + if (mergedComponent == Type.BOGUS) + return Type.OBJECT; + + return createArray(mergedComponent, thisDims); + } + + Type targetRoot; + int targetDims; + + if (typeDims < thisDims) { + targetRoot = typeRoot; + targetDims = typeDims; + } else { + targetRoot = thisRoot; + targetDims = thisDims; + } + + // Special case, arrays are cloneable and serializable, so prefer them when dimensions differ + if (eq(CLONEABLE.clazz, targetRoot.clazz) || eq(SERIALIZABLE.clazz, targetRoot.clazz)) + return createArray(targetRoot, targetDims); + + return createArray(OBJECT, targetDims); + } + + private static CtClass findCommonSuperClass(CtClass one, CtClass two) throws NotFoundException { + CtClass deep = one; + CtClass shallow = two; + CtClass backupShallow = shallow; + CtClass backupDeep = deep; + + // Phase 1 - Find the deepest hierarchy, set deep and shallow correctly + for (;;) { + // In case we get lucky, and find a match early + if (eq(deep, shallow) && deep.getSuperclass() != null) + return deep; + + CtClass deepSuper = deep.getSuperclass(); + CtClass shallowSuper = shallow.getSuperclass(); + + if (shallowSuper == null) { + // right, now reset shallow + shallow = backupShallow; + break; + } + + if (deepSuper == null) { + // wrong, swap them, since deep is now useless, its our tmp before we swap it + deep = backupDeep; + backupDeep = backupShallow; + backupShallow = deep; + + deep = shallow; + shallow = backupShallow; + break; + } + + deep = deepSuper; + shallow = shallowSuper; + } + + // Phase 2 - Move deepBackup up by (deep end - deep) + for (;;) { + deep = deep.getSuperclass(); + if (deep == null) + break; + + backupDeep = backupDeep.getSuperclass(); + } + + deep = backupDeep; + + // Phase 3 - The hierarchy positions are now aligned + // The common super class is easy to find now + while (!eq(deep, shallow)) { + deep = deep.getSuperclass(); + shallow = shallow.getSuperclass(); + } + + return deep; + } + + private Type mergeClasses(Type type) throws NotFoundException { + CtClass superClass = findCommonSuperClass(this.clazz, type.clazz); + + // If its Object, then try and find a common interface(s) + if (superClass.getSuperclass() == null) { + Map interfaces = findCommonInterfaces(type); + if (interfaces.size() == 1) + return new Type((CtClass) interfaces.values().iterator().next()); + if (interfaces.size() > 1) + return new MultiType(interfaces); + + // Only Object is in common + return new Type(superClass); + } + + // Check for a common interface that is not on the found supertype + Map commonDeclared = findExclusiveDeclaredInterfaces(type, superClass); + if (commonDeclared.size() > 0) { + return new MultiType(commonDeclared, new Type(superClass)); + } + + return new Type(superClass); + } + + private Map findCommonInterfaces(Type type) { + Map typeMap = getAllInterfaces(type.clazz, null); + Map thisMap = getAllInterfaces(this.clazz, null); + + return findCommonInterfaces(typeMap, thisMap); + } + + private Map findExclusiveDeclaredInterfaces(Type type, CtClass exclude) { + Map typeMap = getDeclaredInterfaces(type.clazz, null); + Map thisMap = getDeclaredInterfaces(this.clazz, null); + Map excludeMap = getAllInterfaces(exclude, null); + + Iterator i = excludeMap.keySet().iterator(); + while (i.hasNext()) { + Object intf = i.next(); + typeMap.remove(intf); + thisMap.remove(intf); + } + + return findCommonInterfaces(typeMap, thisMap); + } + + + Map findCommonInterfaces(Map typeMap, Map alterMap) { + Iterator i = alterMap.keySet().iterator(); + while (i.hasNext()) { + if (! typeMap.containsKey(i.next())) + i.remove(); + } + + // Reduce to subinterfaces + // This does not need to be recursive since we make a copy, + // and that copy contains all super types for the whole hierarchy + i = new ArrayList(alterMap.values()).iterator(); + while (i.hasNext()) { + CtClass intf = (CtClass) i.next(); + CtClass[] interfaces; + try { + interfaces = intf.getInterfaces(); + } catch (NotFoundException e) { + throw new RuntimeException(e); + } + + for (int c = 0; c < interfaces.length; c++) + alterMap.remove(interfaces[c].getName()); + } + + return alterMap; + } + + Map getAllInterfaces(CtClass clazz, Map map) { + if (map == null) + map = new HashMap(); + + if (clazz.isInterface()) + map.put(clazz.getName(), clazz); + do { + try { + CtClass[] interfaces = clazz.getInterfaces(); + for (int i = 0; i < interfaces.length; i++) { + CtClass intf = interfaces[i]; + map.put(intf.getName(), intf); + getAllInterfaces(intf, map); + } + + clazz = clazz.getSuperclass(); + } catch (NotFoundException e) { + throw new RuntimeException(e); + } + } while (clazz != null); + + return map; + } + + Map getDeclaredInterfaces(CtClass clazz, Map map) { + if (map == null) + map = new HashMap(); + + if (clazz.isInterface()) + map.put(clazz.getName(), clazz); + + CtClass[] interfaces; + try { + interfaces = clazz.getInterfaces(); + } catch (NotFoundException e) { + throw new RuntimeException(e); + } + + for (int i = 0; i < interfaces.length; i++) { + CtClass intf = interfaces[i]; + map.put(intf.getName(), intf); + getDeclaredInterfaces(intf, map); + } + + return map; + } + + public boolean equals(Object o) { + if (! (o instanceof Type)) + return false; + + return o.getClass() == getClass() && eq(clazz, ((Type)o).clazz); + } + + static boolean eq(CtClass one, CtClass two) { + return one == two || (one != null && two != null && one.getName().equals(two.getName())); + } + + public String toString() { + if (this == BOGUS) + return "BOGUS"; + if (this == UNINIT) + return "UNINIT"; + if (this == RETURN_ADDRESS) + return "RETURN ADDRESS"; + if (this == TOP) + return "TOP"; + + return clazz == null ? "null" : clazz.getName(); + } +} diff --git a/src/main/javassist/bytecode/analysis/Util.java b/src/main/javassist/bytecode/analysis/Util.java new file mode 100644 index 0000000..a8cdfcc --- /dev/null +++ b/src/main/javassist/bytecode/analysis/Util.java @@ -0,0 +1,47 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba, and others. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ +package javassist.bytecode.analysis; + +import javassist.bytecode.CodeIterator; +import javassist.bytecode.Opcode; + +/** + * A set of common utility methods. + * + * @author Jason T. Greene + */ +public class Util implements Opcode { + public static int getJumpTarget(int pos, CodeIterator iter) { + int opcode = iter.byteAt(pos); + pos += (opcode == JSR_W || opcode == GOTO_W) ? iter.s32bitAt(pos + 1) : iter.s16bitAt(pos + 1); + return pos; + } + + public static boolean isJumpInstruction(int opcode) { + return (opcode >= IFEQ && opcode <= JSR) || opcode == IFNULL || opcode == IFNONNULL || opcode == JSR_W || opcode == GOTO_W; + } + + public static boolean isGoto(int opcode) { + return opcode == GOTO || opcode == GOTO_W; + } + + public static boolean isJsr(int opcode) { + return opcode == JSR || opcode == JSR_W; + } + + public static boolean isReturn(int opcode) { + return (opcode >= IRETURN && opcode <= RETURN); + } +} diff --git a/src/main/javassist/bytecode/analysis/package.html b/src/main/javassist/bytecode/analysis/package.html new file mode 100644 index 0000000..b141670 --- /dev/null +++ b/src/main/javassist/bytecode/analysis/package.html @@ -0,0 +1,19 @@ +<html> +<body> +Bytecode Analysis API. + +<p>This package provides an API for performing data-flow analysis on a method's bytecode. +This allows the user to determine the type state of the stack and local variable table +at the start of every instruction. In addition this API can be used to validate +bytecode, find dead bytecode, and identify unnecessary checkcasts. + +<p>The users of this package must know the specifications of +class file and Java bytecode. For more details, read this book: + +<ul>Tim Lindholm and Frank Yellin, +"The Java Virtual Machine Specification 2nd Ed.", +Addison-Wesley, 1999. +</ul> + +</body> +</html> diff --git a/src/main/javassist/bytecode/annotation/Annotation.java b/src/main/javassist/bytecode/annotation/Annotation.java new file mode 100644 index 0000000..b48cc8e --- /dev/null +++ b/src/main/javassist/bytecode/annotation/Annotation.java @@ -0,0 +1,347 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 2004 Bill Burke. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.bytecode.annotation; + +import javassist.bytecode.ConstPool; +import javassist.bytecode.Descriptor; +import javassist.ClassPool; +import javassist.CtClass; +import javassist.CtMethod; +import javassist.NotFoundException; + +import java.io.IOException; +import java.util.LinkedHashMap; +import java.util.Set; +import java.util.Iterator; + +/** + * The <code>annotation</code> structure. + * + * <p>An instance of this class is returned by + * <code>getAnnotations()</code> in <code>AnnotationsAttribute</code> + * or in <code>ParameterAnnotationsAttribute</code>. + * + * @see javassist.bytecode.AnnotationsAttribute#getAnnotations() + * @see javassist.bytecode.ParameterAnnotationsAttribute#getAnnotations() + * @see MemberValue + * @see MemberValueVisitor + * @see AnnotationsWriter + * + * @author <a href="mailto:bill@jboss.org">Bill Burke</a> + * @author Shigeru Chiba + * @author <a href="mailto:adrian@jboss.org">Adrian Brock</a> + */ +public class Annotation { + static class Pair { + int name; + MemberValue value; + } + + ConstPool pool; + int typeIndex; + LinkedHashMap members; // this sould be LinkedHashMap + // but it is not supported by JDK 1.3. + + /** + * Constructs an annotation including no members. A member can be + * later added to the created annotation by <code>addMemberValue()</code>. + * + * @param type the index into the constant pool table. + * the entry at that index must be the + * <code>CONSTANT_Utf8_Info</code> structure + * repreenting the name of the annotation interface type. + * @param cp the constant pool table. + * + * @see #addMemberValue(String, MemberValue) + */ + public Annotation(int type, ConstPool cp) { + pool = cp; + typeIndex = type; + members = null; + } + + /** + * Constructs an annotation including no members. A member can be + * later added to the created annotation by <code>addMemberValue()</code>. + * + * @param typeName the name of the annotation interface type. + * @param cp the constant pool table. + * + * @see #addMemberValue(String, MemberValue) + */ + public Annotation(String typeName, ConstPool cp) { + this(cp.addUtf8Info(Descriptor.of(typeName)), cp); + } + + /** + * Constructs an annotation that can be accessed through the interface + * represented by <code>clazz</code>. The values of the members are + * not specified. + * + * @param cp the constant pool table. + * @param clazz the interface. + * @throws NotFoundException when the clazz is not found + */ + public Annotation(ConstPool cp, CtClass clazz) + throws NotFoundException + { + // todo Enums are not supported right now. + this(cp.addUtf8Info(Descriptor.of(clazz.getName())), cp); + + if (!clazz.isInterface()) + throw new RuntimeException( + "Only interfaces are allowed for Annotation creation."); + + CtMethod methods[] = clazz.getDeclaredMethods(); + if (methods.length > 0) { + members = new LinkedHashMap(); + } + + for (int i = 0; i < methods.length; i++) { + CtClass returnType = methods[i].getReturnType(); + addMemberValue(methods[i].getName(), + createMemberValue(cp, returnType)); + + } + } + + /** + * Makes an instance of <code>MemberValue</code>. + * + * @param cp the constant pool table. + * @param type the type of the member. + * @return the member value + * @throws NotFoundException when the type is not found + */ + public static MemberValue createMemberValue(ConstPool cp, CtClass type) + throws NotFoundException + { + if (type == CtClass.booleanType) + return new BooleanMemberValue(cp); + else if (type == CtClass.byteType) + return new ByteMemberValue(cp); + else if (type == CtClass.charType) + return new CharMemberValue(cp); + else if (type == CtClass.shortType) + return new ShortMemberValue(cp); + else if (type == CtClass.intType) + return new IntegerMemberValue(cp); + else if (type == CtClass.longType) + return new LongMemberValue(cp); + else if (type == CtClass.floatType) + return new FloatMemberValue(cp); + else if (type == CtClass.doubleType) + return new DoubleMemberValue(cp); + else if (type.getName().equals("java.lang.Class")) + return new ClassMemberValue(cp); + else if (type.getName().equals("java.lang.String")) + return new StringMemberValue(cp); + else if (type.isArray()) { + CtClass arrayType = type.getComponentType(); + MemberValue member = createMemberValue(cp, arrayType); + return new ArrayMemberValue(member, cp); + } + else if (type.isInterface()) { + Annotation info = new Annotation(cp, type); + return new AnnotationMemberValue(info, cp); + } + else { + // treat as enum. I know this is not typed, + // but JBoss has an Annotation Compiler for JDK 1.4 + // and I want it to work with that. - Bill Burke + EnumMemberValue emv = new EnumMemberValue(cp); + emv.setType(type.getName()); + return emv; + } + } + + /** + * Adds a new member. + * + * @param nameIndex the index into the constant pool table. + * The entry at that index must be + * a <code>CONSTANT_Utf8_info</code> structure. + * structure representing the member name. + * @param value the member value. + */ + public void addMemberValue(int nameIndex, MemberValue value) { + Pair p = new Pair(); + p.name = nameIndex; + p.value = value; + addMemberValue(p); + } + + /** + * Adds a new member. + * + * @param name the member name. + * @param value the member value. + */ + public void addMemberValue(String name, MemberValue value) { + Pair p = new Pair(); + p.name = pool.addUtf8Info(name); + p.value = value; + if (members == null) + members = new LinkedHashMap(); + + members.put(name, p); + } + + private void addMemberValue(Pair pair) { + String name = pool.getUtf8Info(pair.name); + if (members == null) + members = new LinkedHashMap(); + + members.put(name, pair); + } + + /** + * Returns a string representation of the annotation. + */ + public String toString() { + StringBuffer buf = new StringBuffer("@"); + buf.append(getTypeName()); + if (members != null) { + buf.append("("); + Iterator mit = members.keySet().iterator(); + while (mit.hasNext()) { + String name = (String)mit.next(); + buf.append(name).append("=").append(getMemberValue(name)); + if (mit.hasNext()) + buf.append(", "); + } + buf.append(")"); + } + + return buf.toString(); + } + + /** + * Obtains the name of the annotation type. + * + * @return the type name + */ + public String getTypeName() { + return Descriptor.toClassName(pool.getUtf8Info(typeIndex)); + } + + /** + * Obtains all the member names. + * + * @return null if no members are defined. + */ + public Set getMemberNames() { + if (members == null) + return null; + else + return members.keySet(); + } + + /** + * Obtains the member value with the given name. + * + * <p>If this annotation does not have a value for the + * specified member, + * this method returns null. It does not return a + * <code>MemberValue</code> with the default value. + * The default value can be obtained from the annotation type. + * + * @param name the member name + * @return null if the member cannot be found or if the value is + * the default value. + * + * @see javassist.bytecode.AnnotationDefaultAttribute + */ + public MemberValue getMemberValue(String name) { + if (members == null) + return null; + else { + Pair p = (Pair)members.get(name); + if (p == null) + return null; + else + return p.value; + } + } + + /** + * Constructs an annotation-type object representing this annotation. + * For example, if this annotation represents <code>@Author</code>, + * this method returns an <code>Author</code> object. + * + * @param cl class loader for loading an annotation type. + * @param cp class pool for obtaining class files. + * @return the annotation + * @throws ClassNotFoundException if the class cannot found. + * @throws NoSuchClassError if the class linkage fails. + */ + public Object toAnnotationType(ClassLoader cl, ClassPool cp) + throws ClassNotFoundException, NoSuchClassError + { + return AnnotationImpl.make(cl, + MemberValue.loadClass(cl, getTypeName()), + cp, this); + } + + /** + * Writes this annotation. + * + * @param writer the output. + * @throws IOException for an error during the write + */ + public void write(AnnotationsWriter writer) throws IOException { + String typeName = pool.getUtf8Info(typeIndex); + if (members == null) { + writer.annotation(typeName, 0); + return; + } + + writer.annotation(typeName, members.size()); + Iterator it = members.values().iterator(); + while (it.hasNext()) { + Pair pair = (Pair)it.next(); + writer.memberValuePair(pair.name); + pair.value.write(writer); + } + } + + /** + * Returns true if the given object represents the same annotation + * as this object. The equality test checks the member values. + */ + public boolean equals(Object obj) { + if (obj == this) + return true; + if (obj == null || obj instanceof Annotation == false) + return false; + + Annotation other = (Annotation) obj; + + if (getTypeName().equals(other.getTypeName()) == false) + return false; + + LinkedHashMap otherMembers = other.members; + if (members == otherMembers) + return true; + else if (members == null) + return otherMembers == null; + else + if (otherMembers == null) + return false; + else + return members.equals(otherMembers); + } +} diff --git a/src/main/javassist/bytecode/annotation/AnnotationImpl.java b/src/main/javassist/bytecode/annotation/AnnotationImpl.java new file mode 100644 index 0000000..dfd23bb --- /dev/null +++ b/src/main/javassist/bytecode/annotation/AnnotationImpl.java @@ -0,0 +1,304 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.bytecode.annotation; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; + +import javassist.ClassPool; +import javassist.CtClass; +import javassist.NotFoundException; +import javassist.bytecode.AnnotationDefaultAttribute; +import javassist.bytecode.ClassFile; +import javassist.bytecode.MethodInfo; + +/** + * Internal-use only. This is a helper class internally used for implementing + * <code>toAnnotationType()</code> in <code>Annotation</code>. + * + * @author Shigeru Chiba + * @author <a href="mailto:bill@jboss.org">Bill Burke</a> + * @author <a href="mailto:adrian@jboss.org">Adrian Brock</a> + */ +public class AnnotationImpl implements InvocationHandler { + private static final String JDK_ANNOTATION_CLASS_NAME = "java.lang.annotation.Annotation"; + private static Method JDK_ANNOTATION_TYPE_METHOD = null; + + private Annotation annotation; + private ClassPool pool; + private ClassLoader classLoader; + private transient Class annotationType; + private transient int cachedHashCode = Integer.MIN_VALUE; + + static { + // Try to resolve the JDK annotation type method + try { + Class clazz = Class.forName(JDK_ANNOTATION_CLASS_NAME); + JDK_ANNOTATION_TYPE_METHOD = clazz.getMethod("annotationType", (Class[])null); + } + catch (Exception ignored) { + // Probably not JDK5+ + } + } + + /** + * Constructs an annotation object. + * + * @param cl class loader for obtaining annotation types. + * @param clazz the annotation type. + * @param cp class pool for containing an annotation + * type (or null). + * @param anon the annotation. + * @return the annotation + */ + public static Object make(ClassLoader cl, Class clazz, ClassPool cp, + Annotation anon) { + AnnotationImpl handler = new AnnotationImpl(anon, cp, cl); + return Proxy.newProxyInstance(cl, new Class[] { clazz }, handler); + } + + private AnnotationImpl(Annotation a, ClassPool cp, ClassLoader loader) { + annotation = a; + pool = cp; + classLoader = loader; + } + + /** + * Obtains the name of the annotation type. + * + * @return the type name + */ + public String getTypeName() { + return annotation.getTypeName(); + } + + /** + * Get the annotation type + * + * @return the annotation class + * @throws NoClassDefFoundError when the class could not loaded + */ + private Class getAnnotationType() { + if (annotationType == null) { + String typeName = annotation.getTypeName(); + try { + annotationType = classLoader.loadClass(typeName); + } + catch (ClassNotFoundException e) { + NoClassDefFoundError error = new NoClassDefFoundError("Error loading annotation class: " + typeName); + error.setStackTrace(e.getStackTrace()); + throw error; + } + } + return annotationType; + } + + /** + * Obtains the internal data structure representing the annotation. + * + * @return the annotation + */ + public Annotation getAnnotation() { + return annotation; + } + + /** + * Executes a method invocation on a proxy instance. + * The implementations of <code>toString()</code>, <code>equals()</code>, + * and <code>hashCode()</code> are directly supplied by the + * <code>AnnotationImpl</code>. The <code>annotationType()</code> method + * is also available on the proxy instance. + */ + public Object invoke(Object proxy, Method method, Object[] args) + throws Throwable + { + String name = method.getName(); + if (Object.class == method.getDeclaringClass()) { + if ("equals".equals(name)) { + Object obj = args[0]; + return new Boolean(checkEquals(obj)); + } + else if ("toString".equals(name)) + return annotation.toString(); + else if ("hashCode".equals(name)) + return new Integer(hashCode()); + } + else if ("annotationType".equals(name) + && method.getParameterTypes().length == 0) + return getAnnotationType(); + + MemberValue mv = annotation.getMemberValue(name); + if (mv == null) + return getDefault(name, method); + else + return mv.getValue(classLoader, pool, method); + } + + private Object getDefault(String name, Method method) + throws ClassNotFoundException, RuntimeException + { + String classname = annotation.getTypeName(); + if (pool != null) { + try { + CtClass cc = pool.get(classname); + ClassFile cf = cc.getClassFile2(); + MethodInfo minfo = cf.getMethod(name); + if (minfo != null) { + AnnotationDefaultAttribute ainfo + = (AnnotationDefaultAttribute) + minfo.getAttribute(AnnotationDefaultAttribute.tag); + if (ainfo != null) { + MemberValue mv = ainfo.getDefaultValue(); + return mv.getValue(classLoader, pool, method); + } + } + } + catch (NotFoundException e) { + throw new RuntimeException("cannot find a class file: " + + classname); + } + } + + throw new RuntimeException("no default value: " + classname + "." + + name + "()"); + } + + /** + * Returns a hash code value for this object. + */ + public int hashCode() { + if (cachedHashCode == Integer.MIN_VALUE) { + int hashCode = 0; + + // Load the annotation class + getAnnotationType(); + + Method[] methods = annotationType.getDeclaredMethods(); + for (int i = 0; i < methods.length; ++ i) { + String name = methods[i].getName(); + int valueHashCode = 0; + + // Get the value + MemberValue mv = annotation.getMemberValue(name); + Object value = null; + try { + if (mv != null) + value = mv.getValue(classLoader, pool, methods[i]); + if (value == null) + value = getDefault(name, methods[i]); + } + catch (RuntimeException e) { + throw e; + } + catch (Exception e) { + throw new RuntimeException("Error retrieving value " + name + " for annotation " + annotation.getTypeName(), e); + } + + // Calculate the hash code + if (value != null) { + if (value.getClass().isArray()) + valueHashCode = arrayHashCode(value); + else + valueHashCode = value.hashCode(); + } + hashCode += 127 * name.hashCode() ^ valueHashCode; + } + + cachedHashCode = hashCode; + } + return cachedHashCode; + } + + /** + * Check that another annotation equals ourselves. + * + * @param obj the other annotation + * @return the true when equals false otherwise + * @throws Exception for any problem + */ + private boolean checkEquals(Object obj) throws Exception { + if (obj == null) + return false; + + // Optimization when the other is one of ourselves + if (obj instanceof Proxy) { + InvocationHandler ih = Proxy.getInvocationHandler(obj); + if (ih instanceof AnnotationImpl) { + AnnotationImpl other = (AnnotationImpl) ih; + return annotation.equals(other.annotation); + } + } + + Class otherAnnotationType = (Class) JDK_ANNOTATION_TYPE_METHOD.invoke(obj, (Object[])null); + if (getAnnotationType().equals(otherAnnotationType) == false) + return false; + + Method[] methods = annotationType.getDeclaredMethods(); + for (int i = 0; i < methods.length; ++ i) { + String name = methods[i].getName(); + + // Get the value + MemberValue mv = annotation.getMemberValue(name); + Object value = null; + Object otherValue = null; + try { + if (mv != null) + value = mv.getValue(classLoader, pool, methods[i]); + if (value == null) + value = getDefault(name, methods[i]); + otherValue = methods[i].invoke(obj, (Object[])null); + } + catch (RuntimeException e) { + throw e; + } + catch (Exception e) { + throw new RuntimeException("Error retrieving value " + name + " for annotation " + annotation.getTypeName(), e); + } + + if (value == null && otherValue != null) + return false; + if (value != null && value.equals(otherValue) == false) + return false; + } + + return true; + } + + /** + * Calculates the hashCode of an array using the same + * algorithm as java.util.Arrays.hashCode() + * + * @param object the object + * @return the hashCode + */ + private static int arrayHashCode(Object object) + { + if (object == null) + return 0; + + int result = 1; + + Object[] array = (Object[]) object; + for (int i = 0; i < array.length; ++i) { + int elementHashCode = 0; + if (array[i] != null) + elementHashCode = array[i].hashCode(); + result = 31 * result + elementHashCode; + } + return result; + } +} diff --git a/src/main/javassist/bytecode/annotation/AnnotationMemberValue.java b/src/main/javassist/bytecode/annotation/AnnotationMemberValue.java new file mode 100644 index 0000000..368c82a --- /dev/null +++ b/src/main/javassist/bytecode/annotation/AnnotationMemberValue.java @@ -0,0 +1,95 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 2004 Bill Burke. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ +package javassist.bytecode.annotation; + +import javassist.ClassPool; +import javassist.bytecode.ConstPool; +import java.io.IOException; +import java.lang.reflect.Method; + +/** + * Nested annotation. + * + * @author <a href="mailto:bill@jboss.org">Bill Burke</a> + * @author Shigeru Chiba + */ +public class AnnotationMemberValue extends MemberValue { + Annotation value; + + /** + * Constructs an annotation member. The initial value is not specified. + */ + public AnnotationMemberValue(ConstPool cp) { + this(null, cp); + } + + /** + * Constructs an annotation member. The initial value is specified by + * the first parameter. + */ + public AnnotationMemberValue(Annotation a, ConstPool cp) { + super('@', cp); + value = a; + } + + Object getValue(ClassLoader cl, ClassPool cp, Method m) + throws ClassNotFoundException + { + return AnnotationImpl.make(cl, getType(cl), cp, value); + } + + Class getType(ClassLoader cl) throws ClassNotFoundException { + if (value == null) + throw new ClassNotFoundException("no type specified"); + else + return loadClass(cl, value.getTypeName()); + } + + /** + * Obtains the value. + */ + public Annotation getValue() { + return value; + } + + /** + * Sets the value of this member. + */ + public void setValue(Annotation newValue) { + value = newValue; + } + + /** + * Obtains the string representation of this object. + */ + public String toString() { + return value.toString(); + } + + /** + * Writes the value. + */ + public void write(AnnotationsWriter writer) throws IOException { + writer.annotationValue(); + value.write(writer); + } + + /** + * Accepts a visitor. + */ + public void accept(MemberValueVisitor visitor) { + visitor.visitAnnotationMemberValue(this); + } +} diff --git a/src/main/javassist/bytecode/annotation/AnnotationsWriter.java b/src/main/javassist/bytecode/annotation/AnnotationsWriter.java new file mode 100644 index 0000000..f435d8f --- /dev/null +++ b/src/main/javassist/bytecode/annotation/AnnotationsWriter.java @@ -0,0 +1,353 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.bytecode.annotation; + +import java.io.*; + +import javassist.bytecode.ByteArray; +import javassist.bytecode.ConstPool; + +/** + * A convenience class for constructing a + * <code>..Annotations_attribute</code>. + * See the source code of the <code>AnnotationsAttribute.Copier</code> class. + * + * <p>The following code snippet is an example of use of this class: + * + * <ul><pre> + * ConstPool pool = ...; + * output = new ByteArrayOutputStream(); + * writer = new AnnotationsWriter(output, pool); + * + * writer.numAnnotations(1); + * writer.annotation("Author", 2); + * writer.memberValuePair("name"); + * writer.constValueIndex("chiba"); + * writer.memberValuePair("address"); + * writer.constValueIndex("tokyo"); + * + * writer.close(); + * byte[] attribute_info = output.toByteArray(); + * AnnotationsAttribute anno + * = new AnnotationsAttribute(pool, AnnotationsAttribute.visibleTag, + * attribute_info); + * </pre></ul> + * + * <p>The code snippet above generates the annotation attribute + * corresponding to this annotation: + * + * <ul><pre> + * @Author(name = "chiba", address = "tokyo") + * </pre></ul> + * + * @see javassist.bytecode.AnnotationsAttribute + * @see javassist.bytecode.ParameterAnnotationsAttribute + */ +public class AnnotationsWriter { + private OutputStream output; + private ConstPool pool; + + /** + * Constructs with the given output stream. + * + * @param os the output stream. + * @param cp the constant pool. + */ + public AnnotationsWriter(OutputStream os, ConstPool cp) { + output = os; + pool = cp; + } + + /** + * Obtains the constant pool given to the constructor. + */ + public ConstPool getConstPool() { + return pool; + } + + /** + * Closes the output stream. + * + */ + public void close() throws IOException { + output.close(); + } + + /** + * Writes <code>num_parameters</code> in + * <code>Runtime(In)VisibleParameterAnnotations_attribute</code>. + * This method must be followed by <code>num</code> calls to + * <code>numAnnotations()</code>. + */ + public void numParameters(int num) throws IOException { + output.write(num); + } + + /** + * Writes <code>num_annotations</code> in + * <code>Runtime(In)VisibleAnnotations_attribute</code>. + * This method must be followed by <code>num</code> calls to + * <code>annotation()</code>. + */ + public void numAnnotations(int num) throws IOException { + write16bit(num); + } + + /** + * Writes <code>annotation</code>. + * This method must be followed by <code>numMemberValuePairs</code> + * calls to <code>memberValuePair()</code>. + * + * @param type the annotation interface name. + * @param numMemberValuePairs <code>num_member_value_pairs</code> + * in <code>annotation</code>. + */ + public void annotation(String type, int numMemberValuePairs) + throws IOException + { + annotation(pool.addUtf8Info(type), numMemberValuePairs); + } + + /** + * Writes <code>annotation</code>. + * This method must be followed by <code>numMemberValuePairs</code> + * calls to <code>memberValuePair()</code>. + * + * @param typeIndex <code>type_index</code> in <code>annotation</code>. + * @param numMemberValuePairs <code>num_member_value_pairs</code> + * in <code>annotation</code>. + */ + public void annotation(int typeIndex, int numMemberValuePairs) + throws IOException + { + write16bit(typeIndex); + write16bit(numMemberValuePairs); + } + + /** + * Writes an element of a <code>member_value_pairs</code> array + * in <code>annotation</code>. + * This method must be followed by a + * call to <code>constValueIndex()</code>, <code>enumConstValue()</code>, + * etc. + * + * @param memberName the name of the annotation type member. + */ + public void memberValuePair(String memberName) throws IOException { + memberValuePair(pool.addUtf8Info(memberName)); + } + + /** + * Writes an element of a <code>member_value_pairs</code> array + * in <code>annotation</code>. + * This method must be followed by a + * call to <code>constValueIndex()</code>, <code>enumConstValue()</code>, + * etc. + * + * @param memberNameIndex <code>member_name_index</code> + * in <code>member_value_pairs</code> array. + */ + public void memberValuePair(int memberNameIndex) throws IOException { + write16bit(memberNameIndex); + } + + /** + * Writes <code>tag</code> and <code>const_value_index</code> + * in <code>member_value</code>. + * + * @param value the constant value. + */ + public void constValueIndex(boolean value) throws IOException { + constValueIndex('Z', pool.addIntegerInfo(value ? 1 : 0)); + } + + /** + * Writes <code>tag</code> and <code>const_value_index</code> + * in <code>member_value</code>. + * + * @param value the constant value. + */ + public void constValueIndex(byte value) throws IOException { + constValueIndex('B', pool.addIntegerInfo(value)); + } + + /** + * Writes <code>tag</code> and <code>const_value_index</code> + * in <code>member_value</code>. + * + * @param value the constant value. + */ + public void constValueIndex(char value) throws IOException { + constValueIndex('C', pool.addIntegerInfo(value)); + } + + /** + * Writes <code>tag</code> and <code>const_value_index</code> + * in <code>member_value</code>. + * + * @param value the constant value. + */ + public void constValueIndex(short value) throws IOException { + constValueIndex('S', pool.addIntegerInfo(value)); + } + + /** + * Writes <code>tag</code> and <code>const_value_index</code> + * in <code>member_value</code>. + * + * @param value the constant value. + */ + public void constValueIndex(int value) throws IOException { + constValueIndex('I', pool.addIntegerInfo(value)); + } + + /** + * Writes <code>tag</code> and <code>const_value_index</code> + * in <code>member_value</code>. + * + * @param value the constant value. + */ + public void constValueIndex(long value) throws IOException { + constValueIndex('J', pool.addLongInfo(value)); + } + + /** + * Writes <code>tag</code> and <code>const_value_index</code> + * in <code>member_value</code>. + * + * @param value the constant value. + */ + public void constValueIndex(float value) throws IOException { + constValueIndex('F', pool.addFloatInfo(value)); + } + + /** + * Writes <code>tag</code> and <code>const_value_index</code> + * in <code>member_value</code>. + * + * @param value the constant value. + */ + public void constValueIndex(double value) throws IOException { + constValueIndex('D', pool.addDoubleInfo(value)); + } + + /** + * Writes <code>tag</code> and <code>const_value_index</code> + * in <code>member_value</code>. + * + * @param value the constant value. + */ + public void constValueIndex(String value) throws IOException { + constValueIndex('s', pool.addUtf8Info(value)); + } + + /** + * Writes <code>tag</code> and <code>const_value_index</code> + * in <code>member_value</code>. + * + * @param tag <code>tag</code> in <code>member_value</code>. + * @param index <code>const_value_index</code> + * in <code>member_value</code>. + */ + public void constValueIndex(int tag, int index) + throws IOException + { + output.write(tag); + write16bit(index); + } + + /** + * Writes <code>tag</code> and <code>enum_const_value</code> + * in <code>member_value</code>. + * + * @param typeName the type name of the enum constant. + * @param constName the simple name of the enum constant. + */ + public void enumConstValue(String typeName, String constName) + throws IOException + { + enumConstValue(pool.addUtf8Info(typeName), + pool.addUtf8Info(constName)); + } + + /** + * Writes <code>tag</code> and <code>enum_const_value</code> + * in <code>member_value</code>. + * + * @param typeNameIndex <code>type_name_index</code> + * in <code>member_value</code>. + * @param constNameIndex <code>const_name_index</code> + * in <code>member_value</code>. + */ + public void enumConstValue(int typeNameIndex, int constNameIndex) + throws IOException + { + output.write('e'); + write16bit(typeNameIndex); + write16bit(constNameIndex); + } + + /** + * Writes <code>tag</code> and <code>class_info_index</code> + * in <code>member_value</code>. + * + * @param name the class name. + */ + public void classInfoIndex(String name) throws IOException { + classInfoIndex(pool.addUtf8Info(name)); + } + + /** + * Writes <code>tag</code> and <code>class_info_index</code> + * in <code>member_value</code>. + * + * @param index <code>class_info_index</code> + */ + public void classInfoIndex(int index) throws IOException { + output.write('c'); + write16bit(index); + } + + /** + * Writes <code>tag</code> and <code>annotation_value</code> + * in <code>member_value</code>. + * This method must be followed by a call to <code>annotation()</code>. + */ + public void annotationValue() throws IOException { + output.write('@'); + } + + /** + * Writes <code>tag</code> and <code>array_value</code> + * in <code>member_value</code>. + * This method must be followed by <code>numValues</code> calls + * to <code>constValueIndex()</code>, <code>enumConstValue()</code>, + * etc. + * + * @param numValues <code>num_values</code> + * in <code>array_value</code>. + */ + public void arrayValue(int numValues) throws IOException { + output.write('['); + write16bit(numValues); + } + + private void write16bit(int value) throws IOException { + byte[] buf = new byte[2]; + ByteArray.write16bit(value, buf, 0); + output.write(buf); + } +} diff --git a/src/main/javassist/bytecode/annotation/ArrayMemberValue.java b/src/main/javassist/bytecode/annotation/ArrayMemberValue.java new file mode 100644 index 0000000..75ffe13 --- /dev/null +++ b/src/main/javassist/bytecode/annotation/ArrayMemberValue.java @@ -0,0 +1,144 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 2004 Bill Burke. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ +package javassist.bytecode.annotation; + +import javassist.ClassPool; +import javassist.bytecode.ConstPool; +import java.io.IOException; +import java.lang.reflect.Array; +import java.lang.reflect.Method; + +/** + * Array member. + * + * @author <a href="mailto:bill@jboss.org">Bill Burke</a> + * @author Shigeru Chiba + */ +public class ArrayMemberValue extends MemberValue { + MemberValue type; + MemberValue[] values; + + /** + * Constructs an array. The initial value or type are not specified. + */ + public ArrayMemberValue(ConstPool cp) { + super('[', cp); + type = null; + values = null; + } + + /** + * Constructs an array. The initial value is not specified. + * + * @param t the type of the array elements. + */ + public ArrayMemberValue(MemberValue t, ConstPool cp) { + super('[', cp); + type = t; + values = null; + } + + Object getValue(ClassLoader cl, ClassPool cp, Method method) + throws ClassNotFoundException + { + if (values == null) + throw new ClassNotFoundException( + "no array elements found: " + method.getName()); + + int size = values.length; + Class clazz; + if (type == null) { + clazz = method.getReturnType().getComponentType(); + if (clazz == null || size > 0) + throw new ClassNotFoundException("broken array type: " + + method.getName()); + } + else + clazz = type.getType(cl); + + Object a = Array.newInstance(clazz, size); + for (int i = 0; i < size; i++) + Array.set(a, i, values[i].getValue(cl, cp, method)); + + return a; + } + + Class getType(ClassLoader cl) throws ClassNotFoundException { + if (type == null) + throw new ClassNotFoundException("no array type specified"); + + Object a = Array.newInstance(type.getType(cl), 0); + return a.getClass(); + } + + /** + * Obtains the type of the elements. + * + * @return null if the type is not specified. + */ + public MemberValue getType() { + return type; + } + + /** + * Obtains the elements of the array. + */ + public MemberValue[] getValue() { + return values; + } + + /** + * Sets the elements of the array. + */ + public void setValue(MemberValue[] elements) { + values = elements; + if (elements != null && elements.length > 0) + type = elements[0]; + } + + /** + * Obtains the string representation of this object. + */ + public String toString() { + StringBuffer buf = new StringBuffer("{"); + if (values != null) { + for (int i = 0; i < values.length; i++) { + buf.append(values[i].toString()); + if (i + 1 < values.length) + buf.append(", "); + } + } + + buf.append("}"); + return buf.toString(); + } + + /** + * Writes the value. + */ + public void write(AnnotationsWriter writer) throws IOException { + int num = values.length; + writer.arrayValue(num); + for (int i = 0; i < num; ++i) + values[i].write(writer); + } + + /** + * Accepts a visitor. + */ + public void accept(MemberValueVisitor visitor) { + visitor.visitArrayMemberValue(this); + } +} diff --git a/src/main/javassist/bytecode/annotation/BooleanMemberValue.java b/src/main/javassist/bytecode/annotation/BooleanMemberValue.java new file mode 100644 index 0000000..29432b1 --- /dev/null +++ b/src/main/javassist/bytecode/annotation/BooleanMemberValue.java @@ -0,0 +1,102 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 2004 Bill Burke. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ +package javassist.bytecode.annotation; + +import javassist.ClassPool; +import javassist.bytecode.ConstPool; +import java.io.IOException; +import java.lang.reflect.Method; + +/** + * Boolean constant value. + * + * @author <a href="mailto:bill@jboss.org">Bill Burke</a> + * @author Shigeru Chiba + */ +public class BooleanMemberValue extends MemberValue { + int valueIndex; + + /** + * Constructs a boolean constant value. The initial value is specified + * by the constant pool entry at the given index. + * + * @param index the index of a CONSTANT_Integer_info structure. + */ + public BooleanMemberValue(int index, ConstPool cp) { + super('Z', cp); + this.valueIndex = index; + } + + /** + * Constructs a boolean constant value. + * + * @param b the initial value. + */ + public BooleanMemberValue(boolean b, ConstPool cp) { + super('Z', cp); + setValue(b); + } + + /** + * Constructs a boolean constant value. The initial value is false. + */ + public BooleanMemberValue(ConstPool cp) { + super('Z', cp); + setValue(false); + } + + Object getValue(ClassLoader cl, ClassPool cp, Method m) { + return new Boolean(getValue()); + } + + Class getType(ClassLoader cl) { + return boolean.class; + } + + /** + * Obtains the value of the member. + */ + public boolean getValue() { + return cp.getIntegerInfo(valueIndex) != 0; + } + + /** + * Sets the value of the member. + */ + public void setValue(boolean newValue) { + valueIndex = cp.addIntegerInfo(newValue ? 1 : 0); + } + + /** + * Obtains the string representation of this object. + */ + public String toString() { + return getValue() ? "true" : "false"; + } + + /** + * Writes the value. + */ + public void write(AnnotationsWriter writer) throws IOException { + writer.constValueIndex(getValue()); + } + + /** + * Accepts a visitor. + */ + public void accept(MemberValueVisitor visitor) { + visitor.visitBooleanMemberValue(this); + } +} diff --git a/src/main/javassist/bytecode/annotation/ByteMemberValue.java b/src/main/javassist/bytecode/annotation/ByteMemberValue.java new file mode 100644 index 0000000..90763b0 --- /dev/null +++ b/src/main/javassist/bytecode/annotation/ByteMemberValue.java @@ -0,0 +1,102 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 2004 Bill Burke. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ +package javassist.bytecode.annotation; + +import javassist.ClassPool; +import javassist.bytecode.ConstPool; +import java.io.IOException; +import java.lang.reflect.Method; + +/** + * Byte constant value. + * + * @author <a href="mailto:bill@jboss.org">Bill Burke</a> + * @author Shigeru Chiba + */ +public class ByteMemberValue extends MemberValue { + int valueIndex; + + /** + * Constructs a byte constant value. The initial value is specified + * by the constant pool entry at the given index. + * + * @param index the index of a CONSTANT_Integer_info structure. + */ + public ByteMemberValue(int index, ConstPool cp) { + super('B', cp); + this.valueIndex = index; + } + + /** + * Constructs a byte constant value. + * + * @param b the initial value. + */ + public ByteMemberValue(byte b, ConstPool cp) { + super('B', cp); + setValue(b); + } + + /** + * Constructs a byte constant value. The initial value is 0. + */ + public ByteMemberValue(ConstPool cp) { + super('B', cp); + setValue((byte)0); + } + + Object getValue(ClassLoader cl, ClassPool cp, Method m) { + return new Byte(getValue()); + } + + Class getType(ClassLoader cl) { + return byte.class; + } + + /** + * Obtains the value of the member. + */ + public byte getValue() { + return (byte)cp.getIntegerInfo(valueIndex); + } + + /** + * Sets the value of the member. + */ + public void setValue(byte newValue) { + valueIndex = cp.addIntegerInfo(newValue); + } + + /** + * Obtains the string representation of this object. + */ + public String toString() { + return Byte.toString(getValue()); + } + + /** + * Writes the value. + */ + public void write(AnnotationsWriter writer) throws IOException { + writer.constValueIndex(getValue()); + } + + /** + * Accepts a visitor. + */ + public void accept(MemberValueVisitor visitor) { + visitor.visitByteMemberValue(this); + } +} diff --git a/src/main/javassist/bytecode/annotation/CharMemberValue.java b/src/main/javassist/bytecode/annotation/CharMemberValue.java new file mode 100644 index 0000000..f6691df --- /dev/null +++ b/src/main/javassist/bytecode/annotation/CharMemberValue.java @@ -0,0 +1,103 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 2004 Bill Burke. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.bytecode.annotation; + +import javassist.ClassPool; +import javassist.bytecode.ConstPool; +import java.io.IOException; +import java.lang.reflect.Method; + +/** + * Char constant value. + * + * @author <a href="mailto:bill@jboss.org">Bill Burke</a> + * @author Shigeru Chiba + */ +public class CharMemberValue extends MemberValue { + int valueIndex; + + /** + * Constructs a char constant value. The initial value is specified + * by the constant pool entry at the given index. + * + * @param index the index of a CONSTANT_Integer_info structure. + */ + public CharMemberValue(int index, ConstPool cp) { + super('C', cp); + this.valueIndex = index; + } + + /** + * Constructs a char constant value. + * + * @param c the initial value. + */ + public CharMemberValue(char c, ConstPool cp) { + super('C', cp); + setValue(c); + } + + /** + * Constructs a char constant value. The initial value is '\0'. + */ + public CharMemberValue(ConstPool cp) { + super('C', cp); + setValue('\0'); + } + + Object getValue(ClassLoader cl, ClassPool cp, Method m) { + return new Character(getValue()); + } + + Class getType(ClassLoader cl) { + return char.class; + } + + /** + * Obtains the value of the member. + */ + public char getValue() { + return (char)cp.getIntegerInfo(valueIndex); + } + + /** + * Sets the value of the member. + */ + public void setValue(char newValue) { + valueIndex = cp.addIntegerInfo(newValue); + } + + /** + * Obtains the string representation of this object. + */ + public String toString() { + return Character.toString(getValue()); + } + + /** + * Writes the value. + */ + public void write(AnnotationsWriter writer) throws IOException { + writer.constValueIndex(getValue()); + } + + /** + * Accepts a visitor. + */ + public void accept(MemberValueVisitor visitor) { + visitor.visitCharMemberValue(this); + } +} diff --git a/src/main/javassist/bytecode/annotation/ClassMemberValue.java b/src/main/javassist/bytecode/annotation/ClassMemberValue.java new file mode 100644 index 0000000..c29dbb2 --- /dev/null +++ b/src/main/javassist/bytecode/annotation/ClassMemberValue.java @@ -0,0 +1,132 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 2004 Bill Burke. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.bytecode.annotation; + +import javassist.ClassPool; +import javassist.bytecode.ConstPool; +import javassist.bytecode.Descriptor; +import java.io.IOException; +import java.lang.reflect.Method; + +/** + * Class value. + * + * @author <a href="mailto:bill@jboss.org">Bill Burke</a> + * @author Shigeru Chiba + */ +public class ClassMemberValue extends MemberValue { + int valueIndex; + + /** + * Constructs a class value. The initial value is specified + * by the constant pool entry at the given index. + * + * @param index the index of a CONSTANT_Utf8_info structure. + */ + public ClassMemberValue(int index, ConstPool cp) { + super('c', cp); + this.valueIndex = index; + } + + /** + * Constructs a class value. + * + * @param className the initial value. + */ + public ClassMemberValue(String className, ConstPool cp) { + super('c', cp); + setValue(className); + } + + /** + * Constructs a class value. + * The initial value is java.lang.Class. + */ + public ClassMemberValue(ConstPool cp) { + super('c', cp); + setValue("java.lang.Class"); + } + + Object getValue(ClassLoader cl, ClassPool cp, Method m) + throws ClassNotFoundException { + final String classname = getValue(); + if (classname.equals("void")) + return void.class; + else if (classname.equals("int")) + return int.class; + else if (classname.equals("byte")) + return byte.class; + else if (classname.equals("long")) + return long.class; + else if (classname.equals("double")) + return double.class; + else if (classname.equals("float")) + return float.class; + else if (classname.equals("char")) + return char.class; + else if (classname.equals("short")) + return short.class; + else if (classname.equals("boolean")) + return boolean.class; + else + return loadClass(cl, classname); + } + + Class getType(ClassLoader cl) throws ClassNotFoundException { + return loadClass(cl, "java.lang.Class"); + } + + /** + * Obtains the value of the member. + * + * @return fully-qualified class name. + */ + public String getValue() { + String v = cp.getUtf8Info(valueIndex); + return Descriptor.toClassName(v); + } + + /** + * Sets the value of the member. + * + * @param newClassName fully-qualified class name. + */ + public void setValue(String newClassName) { + String setTo = Descriptor.of(newClassName); + valueIndex = cp.addUtf8Info(setTo); + } + + /** + * Obtains the string representation of this object. + */ + public String toString() { + return "<" + getValue() + " class>"; + } + + /** + * Writes the value. + */ + public void write(AnnotationsWriter writer) throws IOException { + writer.classInfoIndex(cp.getUtf8Info(valueIndex)); + } + + /** + * Accepts a visitor. + */ + public void accept(MemberValueVisitor visitor) { + visitor.visitClassMemberValue(this); + } +} diff --git a/src/main/javassist/bytecode/annotation/DoubleMemberValue.java b/src/main/javassist/bytecode/annotation/DoubleMemberValue.java new file mode 100644 index 0000000..f2825ad --- /dev/null +++ b/src/main/javassist/bytecode/annotation/DoubleMemberValue.java @@ -0,0 +1,104 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 2004 Bill Burke. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.bytecode.annotation; + +import javassist.ClassPool; +import javassist.bytecode.ConstPool; +import java.io.IOException; +import java.lang.reflect.Method; + +/** + * Double floating-point number constant value. + * + * @author <a href="mailto:bill@jboss.org">Bill Burke</a> + * @author Shigeru Chiba + * @version $Revision: 1.7 $ + */ +public class DoubleMemberValue extends MemberValue { + int valueIndex; + + /** + * Constructs a double constant value. The initial value is specified + * by the constant pool entry at the given index. + * + * @param index the index of a CONSTANT_Double_info structure. + */ + public DoubleMemberValue(int index, ConstPool cp) { + super('D', cp); + this.valueIndex = index; + } + + /** + * Constructs a double constant value. + * + * @param d the initial value. + */ + public DoubleMemberValue(double d, ConstPool cp) { + super('D', cp); + setValue(d); + } + + /** + * Constructs a double constant value. The initial value is 0.0. + */ + public DoubleMemberValue(ConstPool cp) { + super('D', cp); + setValue(0.0); + } + + Object getValue(ClassLoader cl, ClassPool cp, Method m) { + return new Double(getValue()); + } + + Class getType(ClassLoader cl) { + return double.class; + } + + /** + * Obtains the value of the member. + */ + public double getValue() { + return cp.getDoubleInfo(valueIndex); + } + + /** + * Sets the value of the member. + */ + public void setValue(double newValue) { + valueIndex = cp.addDoubleInfo(newValue); + } + + /** + * Obtains the string representation of this object. + */ + public String toString() { + return Double.toString(getValue()); + } + + /** + * Writes the value. + */ + public void write(AnnotationsWriter writer) throws IOException { + writer.constValueIndex(getValue()); + } + + /** + * Accepts a visitor. + */ + public void accept(MemberValueVisitor visitor) { + visitor.visitDoubleMemberValue(this); + } +} diff --git a/src/main/javassist/bytecode/annotation/EnumMemberValue.java b/src/main/javassist/bytecode/annotation/EnumMemberValue.java new file mode 100644 index 0000000..effec09 --- /dev/null +++ b/src/main/javassist/bytecode/annotation/EnumMemberValue.java @@ -0,0 +1,125 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 2004 Bill Burke. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.bytecode.annotation; + +import java.io.IOException; +import java.lang.reflect.Method; + +import javassist.ClassPool; +import javassist.bytecode.ConstPool; +import javassist.bytecode.Descriptor; + +/** + * Enum constant value. + * + * @author <a href="mailto:bill@jboss.org">Bill Burke</a> + * @author Shigeru Chiba + */ +public class EnumMemberValue extends MemberValue { + int typeIndex, valueIndex; + + /** + * Constructs an enum constant value. The initial value is specified + * by the constant pool entries at the given indexes. + * + * @param type the index of a CONSTANT_Utf8_info structure + * representing the enum type. + * @param value the index of a CONSTANT_Utf8_info structure. + * representing the enum value. + */ + public EnumMemberValue(int type, int value, ConstPool cp) { + super('e', cp); + this.typeIndex = type; + this.valueIndex = value; + } + + /** + * Constructs an enum constant value. + * The initial value is not specified. + */ + public EnumMemberValue(ConstPool cp) { + super('e', cp); + typeIndex = valueIndex = 0; + } + + Object getValue(ClassLoader cl, ClassPool cp, Method m) + throws ClassNotFoundException + { + try { + return getType(cl).getField(getValue()).get(null); + } + catch (NoSuchFieldException e) { + throw new ClassNotFoundException(getType() + "." + getValue()); + } + catch (IllegalAccessException e) { + throw new ClassNotFoundException(getType() + "." + getValue()); + } + } + + Class getType(ClassLoader cl) throws ClassNotFoundException { + return loadClass(cl, getType()); + } + + /** + * Obtains the enum type name. + * + * @return a fully-qualified type name. + */ + public String getType() { + return Descriptor.toClassName(cp.getUtf8Info(typeIndex)); + } + + /** + * Changes the enum type name. + * + * @param typename a fully-qualified type name. + */ + public void setType(String typename) { + typeIndex = cp.addUtf8Info(Descriptor.of(typename)); + } + + /** + * Obtains the name of the enum constant value. + */ + public String getValue() { + return cp.getUtf8Info(valueIndex); + } + + /** + * Changes the name of the enum constant value. + */ + public void setValue(String name) { + valueIndex = cp.addUtf8Info(name); + } + + public String toString() { + return getType() + "." + getValue(); + } + + /** + * Writes the value. + */ + public void write(AnnotationsWriter writer) throws IOException { + writer.enumConstValue(cp.getUtf8Info(typeIndex), getValue()); + } + + /** + * Accepts a visitor. + */ + public void accept(MemberValueVisitor visitor) { + visitor.visitEnumMemberValue(this); + } +} diff --git a/src/main/javassist/bytecode/annotation/FloatMemberValue.java b/src/main/javassist/bytecode/annotation/FloatMemberValue.java new file mode 100644 index 0000000..7188b5e --- /dev/null +++ b/src/main/javassist/bytecode/annotation/FloatMemberValue.java @@ -0,0 +1,104 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 2004 Bill Burke. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.bytecode.annotation; + +import javassist.ClassPool; +import javassist.bytecode.ConstPool; +import java.io.IOException; +import java.lang.reflect.Method; + +/** + * Floating-point number constant value. + * + * @author <a href="mailto:bill@jboss.org">Bill Burke</a> + * @author Shigeru Chiba + * @version $Revision: 1.7 $ + */ +public class FloatMemberValue extends MemberValue { + int valueIndex; + + /** + * Constructs a float constant value. The initial value is specified + * by the constant pool entry at the given index. + * + * @param index the index of a CONSTANT_Float_info structure. + */ + public FloatMemberValue(int index, ConstPool cp) { + super('F', cp); + this.valueIndex = index; + } + + /** + * Constructs a float constant value. + * + * @param f the initial value. + */ + public FloatMemberValue(float f, ConstPool cp) { + super('F', cp); + setValue(f); + } + + /** + * Constructs a float constant value. The initial value is 0.0. + */ + public FloatMemberValue(ConstPool cp) { + super('F', cp); + setValue(0.0F); + } + + Object getValue(ClassLoader cl, ClassPool cp, Method m) { + return new Float(getValue()); + } + + Class getType(ClassLoader cl) { + return float.class; + } + + /** + * Obtains the value of the member. + */ + public float getValue() { + return cp.getFloatInfo(valueIndex); + } + + /** + * Sets the value of the member. + */ + public void setValue(float newValue) { + valueIndex = cp.addFloatInfo(newValue); + } + + /** + * Obtains the string representation of this object. + */ + public String toString() { + return Float.toString(getValue()); + } + + /** + * Writes the value. + */ + public void write(AnnotationsWriter writer) throws IOException { + writer.constValueIndex(getValue()); + } + + /** + * Accepts a visitor. + */ + public void accept(MemberValueVisitor visitor) { + visitor.visitFloatMemberValue(this); + } +} diff --git a/src/main/javassist/bytecode/annotation/IntegerMemberValue.java b/src/main/javassist/bytecode/annotation/IntegerMemberValue.java new file mode 100644 index 0000000..2f2a907 --- /dev/null +++ b/src/main/javassist/bytecode/annotation/IntegerMemberValue.java @@ -0,0 +1,109 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 2004 Bill Burke. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.bytecode.annotation; + +import javassist.ClassPool; +import javassist.bytecode.ConstPool; +import java.io.IOException; +import java.lang.reflect.Method; + +/** + * Integer constant value. + * + * @author <a href="mailto:bill@jboss.org">Bill Burke</a> + * @author Shigeru Chiba + */ +public class IntegerMemberValue extends MemberValue { + int valueIndex; + + /** + * Constructs an int constant value. The initial value is specified + * by the constant pool entry at the given index. + * + * @param index the index of a CONSTANT_Integer_info structure. + */ + public IntegerMemberValue(int index, ConstPool cp) { + super('I', cp); + this.valueIndex = index; + } + + /** + * Constructs an int constant value. + * Note that this constructor receives <b>the initial value + * as the second parameter</b> + * unlike the corresponding constructors in the sibling classes. + * This is for making a difference from the constructor that receives + * an index into the constant pool table as the first parameter. + * Note that the index is also int type. + * + * @param value the initial value. + */ + public IntegerMemberValue(ConstPool cp, int value) { + super('I', cp); + setValue(value); + } + + /** + * Constructs an int constant value. The initial value is 0. + */ + public IntegerMemberValue(ConstPool cp) { + super('I', cp); + setValue(0); + } + + Object getValue(ClassLoader cl, ClassPool cp, Method m) { + return new Integer(getValue()); + } + + Class getType(ClassLoader cl) { + return int.class; + } + + /** + * Obtains the value of the member. + */ + public int getValue() { + return cp.getIntegerInfo(valueIndex); + } + + /** + * Sets the value of the member. + */ + public void setValue(int newValue) { + valueIndex = cp.addIntegerInfo(newValue); + } + + /** + * Obtains the string representation of this object. + */ + public String toString() { + return Integer.toString(getValue()); + } + + /** + * Writes the value. + */ + public void write(AnnotationsWriter writer) throws IOException { + writer.constValueIndex(getValue()); + } + + /** + * Accepts a visitor. + */ + public void accept(MemberValueVisitor visitor) { + visitor.visitIntegerMemberValue(this); + } +} diff --git a/src/main/javassist/bytecode/annotation/LongMemberValue.java b/src/main/javassist/bytecode/annotation/LongMemberValue.java new file mode 100644 index 0000000..2afd4a0 --- /dev/null +++ b/src/main/javassist/bytecode/annotation/LongMemberValue.java @@ -0,0 +1,103 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 2004 Bill Burke. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.bytecode.annotation; + +import javassist.ClassPool; +import javassist.bytecode.ConstPool; +import java.io.IOException; +import java.lang.reflect.Method; + +/** + * Long integer constant value. + * + * @author <a href="mailto:bill@jboss.org">Bill Burke</a> + * @author Shigeru Chiba + */ +public class LongMemberValue extends MemberValue { + int valueIndex; + + /** + * Constructs a long constant value. The initial value is specified + * by the constant pool entry at the given index. + * + * @param index the index of a CONSTANT_Long_info structure. + */ + public LongMemberValue(int index, ConstPool cp) { + super('J', cp); + this.valueIndex = index; + } + + /** + * Constructs a long constant value. + * + * @param j the initial value. + */ + public LongMemberValue(long j, ConstPool cp) { + super('J', cp); + setValue(j); + } + + /** + * Constructs a long constant value. The initial value is 0. + */ + public LongMemberValue(ConstPool cp) { + super('J', cp); + setValue(0L); + } + + Object getValue(ClassLoader cl, ClassPool cp, Method m) { + return new Long(getValue()); + } + + Class getType(ClassLoader cl) { + return long.class; + } + + /** + * Obtains the value of the member. + */ + public long getValue() { + return cp.getLongInfo(valueIndex); + } + + /** + * Sets the value of the member. + */ + public void setValue(long newValue) { + valueIndex = cp.addLongInfo(newValue); + } + + /** + * Obtains the string representation of this object. + */ + public String toString() { + return Long.toString(getValue()); + } + + /** + * Writes the value. + */ + public void write(AnnotationsWriter writer) throws IOException { + writer.constValueIndex(getValue()); + } + + /** + * Accepts a visitor. + */ + public void accept(MemberValueVisitor visitor) { + visitor.visitLongMemberValue(this); + } +} diff --git a/src/main/javassist/bytecode/annotation/MemberValue.java b/src/main/javassist/bytecode/annotation/MemberValue.java new file mode 100644 index 0000000..18796ee --- /dev/null +++ b/src/main/javassist/bytecode/annotation/MemberValue.java @@ -0,0 +1,88 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 2004 Bill Burke. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.bytecode.annotation; + +import javassist.ClassPool; +import javassist.bytecode.ConstPool; +import javassist.bytecode.Descriptor; + +import java.io.IOException; +import java.lang.reflect.Array; +import java.lang.reflect.Method; + +/** + * The value of a member declared in an annotation. + * + * @see Annotation#getMemberValue(String) + * @author <a href="mailto:bill@jboss.org">Bill Burke</a> + * @author Shigeru Chiba + */ +public abstract class MemberValue { + ConstPool cp; + char tag; + + MemberValue(char tag, ConstPool cp) { + this.cp = cp; + this.tag = tag; + } + + /** + * Returns the value. If the value type is a primitive type, the + * returned value is boxed. + */ + abstract Object getValue(ClassLoader cl, ClassPool cp, Method m) + throws ClassNotFoundException; + + abstract Class getType(ClassLoader cl) throws ClassNotFoundException; + + static Class loadClass(ClassLoader cl, String classname) + throws ClassNotFoundException, NoSuchClassError + { + try { + return Class.forName(convertFromArray(classname), true, cl); + } + catch (LinkageError e) { + throw new NoSuchClassError(classname, e); + } + } + + private static String convertFromArray(String classname) + { + int index = classname.indexOf("[]"); + if (index != -1) { + String rawType = classname.substring(0, index); + StringBuffer sb = new StringBuffer(Descriptor.of(rawType)); + while (index != -1) { + sb.insert(0, "["); + index = classname.indexOf("[]", index + 1); + } + return sb.toString().replace('/', '.'); + } + return classname; + } + + /** + * Accepts a visitor. + */ + public abstract void accept(MemberValueVisitor visitor); + + /** + * Writes the value. + */ + public abstract void write(AnnotationsWriter w) throws IOException; +} + + diff --git a/src/main/javassist/bytecode/annotation/MemberValueVisitor.java b/src/main/javassist/bytecode/annotation/MemberValueVisitor.java new file mode 100644 index 0000000..6944bd0 --- /dev/null +++ b/src/main/javassist/bytecode/annotation/MemberValueVisitor.java @@ -0,0 +1,38 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 2004 Bill Burke. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.bytecode.annotation; + +/** + * Visitor for traversing member values included in an annotation. + * + * @see MemberValue#accept(MemberValueVisitor) + * @author <a href="mailto:bill@jboss.org">Bill Burke</a> + */ +public interface MemberValueVisitor { + public void visitAnnotationMemberValue(AnnotationMemberValue node); + public void visitArrayMemberValue(ArrayMemberValue node); + public void visitBooleanMemberValue(BooleanMemberValue node); + public void visitByteMemberValue(ByteMemberValue node); + public void visitCharMemberValue(CharMemberValue node); + public void visitDoubleMemberValue(DoubleMemberValue node); + public void visitEnumMemberValue(EnumMemberValue node); + public void visitFloatMemberValue(FloatMemberValue node); + public void visitIntegerMemberValue(IntegerMemberValue node); + public void visitLongMemberValue(LongMemberValue node); + public void visitShortMemberValue(ShortMemberValue node); + public void visitStringMemberValue(StringMemberValue node); + public void visitClassMemberValue(ClassMemberValue node); +} diff --git a/src/main/javassist/bytecode/annotation/NoSuchClassError.java b/src/main/javassist/bytecode/annotation/NoSuchClassError.java new file mode 100644 index 0000000..c6d1a12 --- /dev/null +++ b/src/main/javassist/bytecode/annotation/NoSuchClassError.java @@ -0,0 +1,39 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2009 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.bytecode.annotation; + +/** + * Thrown if the linkage fails. + * It keeps the name of the class that caused this error. + */ +public class NoSuchClassError extends Error { + private String className; + + /** + * Constructs an exception. + */ + public NoSuchClassError(String className, Error cause) { + super(cause.toString(), cause); + this.className = className; + } + + /** + * Returns the name of the class not found. + */ + public String getClassName() { + return className; + } +} diff --git a/src/main/javassist/bytecode/annotation/ShortMemberValue.java b/src/main/javassist/bytecode/annotation/ShortMemberValue.java new file mode 100644 index 0000000..3ccf380 --- /dev/null +++ b/src/main/javassist/bytecode/annotation/ShortMemberValue.java @@ -0,0 +1,103 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 2004 Bill Burke. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.bytecode.annotation; + +import javassist.ClassPool; +import javassist.bytecode.ConstPool; +import java.io.IOException; +import java.lang.reflect.Method; + +/** + * Short integer constant value. + * + * @author <a href="mailto:bill@jboss.org">Bill Burke</a> + * @author Shigeru Chiba + */ +public class ShortMemberValue extends MemberValue { + int valueIndex; + + /** + * Constructs a short constant value. The initial value is specified + * by the constant pool entry at the given index. + * + * @param index the index of a CONSTANT_Integer_info structure. + */ + public ShortMemberValue(int index, ConstPool cp) { + super('S', cp); + this.valueIndex = index; + } + + /** + * Constructs a short constant value. + * + * @param s the initial value. + */ + public ShortMemberValue(short s, ConstPool cp) { + super('S', cp); + setValue(s); + } + + /** + * Constructs a short constant value. The initial value is 0. + */ + public ShortMemberValue(ConstPool cp) { + super('S', cp); + setValue((short)0); + } + + Object getValue(ClassLoader cl, ClassPool cp, Method m) { + return new Short(getValue()); + } + + Class getType(ClassLoader cl) { + return short.class; + } + + /** + * Obtains the value of the member. + */ + public short getValue() { + return (short)cp.getIntegerInfo(valueIndex); + } + + /** + * Sets the value of the member. + */ + public void setValue(short newValue) { + valueIndex = cp.addIntegerInfo(newValue); + } + + /** + * Obtains the string representation of this object. + */ + public String toString() { + return Short.toString(getValue()); + } + + /** + * Writes the value. + */ + public void write(AnnotationsWriter writer) throws IOException { + writer.constValueIndex(getValue()); + } + + /** + * Accepts a visitor. + */ + public void accept(MemberValueVisitor visitor) { + visitor.visitShortMemberValue(this); + } +} diff --git a/src/main/javassist/bytecode/annotation/StringMemberValue.java b/src/main/javassist/bytecode/annotation/StringMemberValue.java new file mode 100644 index 0000000..970fb8f --- /dev/null +++ b/src/main/javassist/bytecode/annotation/StringMemberValue.java @@ -0,0 +1,103 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 2004 Bill Burke. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.bytecode.annotation; + +import javassist.ClassPool; +import javassist.bytecode.ConstPool; +import java.io.IOException; +import java.lang.reflect.Method; + +/** + * String constant value. + * + * @author <a href="mailto:bill@jboss.org">Bill Burke</a> + * @author Shigeru Chiba + */ +public class StringMemberValue extends MemberValue { + int valueIndex; + + /** + * Constructs a string constant value. The initial value is specified + * by the constant pool entry at the given index. + * + * @param index the index of a CONSTANT_Utf8_info structure. + */ + public StringMemberValue(int index, ConstPool cp) { + super('s', cp); + this.valueIndex = index; + } + + /** + * Constructs a string constant value. + * + * @param str the initial value. + */ + public StringMemberValue(String str, ConstPool cp) { + super('s', cp); + setValue(str); + } + + /** + * Constructs a string constant value. The initial value is "". + */ + public StringMemberValue(ConstPool cp) { + super('s', cp); + setValue(""); + } + + Object getValue(ClassLoader cl, ClassPool cp, Method m) { + return getValue(); + } + + Class getType(ClassLoader cl) { + return String.class; + } + + /** + * Obtains the value of the member. + */ + public String getValue() { + return cp.getUtf8Info(valueIndex); + } + + /** + * Sets the value of the member. + */ + public void setValue(String newValue) { + valueIndex = cp.addUtf8Info(newValue); + } + + /** + * Obtains the string representation of this object. + */ + public String toString() { + return "\"" + getValue() + "\""; + } + + /** + * Writes the value. + */ + public void write(AnnotationsWriter writer) throws IOException { + writer.constValueIndex(getValue()); + } + + /** + * Accepts a visitor. + */ + public void accept(MemberValueVisitor visitor) { + visitor.visitStringMemberValue(this); + } +} diff --git a/src/main/javassist/bytecode/annotation/package.html b/src/main/javassist/bytecode/annotation/package.html new file mode 100644 index 0000000..d0656db --- /dev/null +++ b/src/main/javassist/bytecode/annotation/package.html @@ -0,0 +1,8 @@ +<html> +<body> +Bytecode-level Annotations API. + +<p>This package provides low-level API for editing annotations attributes. + +</body> +</html> diff --git a/src/main/javassist/bytecode/package.html b/src/main/javassist/bytecode/package.html new file mode 100644 index 0000000..9da3888 --- /dev/null +++ b/src/main/javassist/bytecode/package.html @@ -0,0 +1,18 @@ +<html> +<body> +Bytecode-level API. + +<p>This package provides low-level API for editing a raw class file. +It allows the users to read and modify a constant pool entry, a single +bytecode instruction, and so on. + +<p>The users of this package must know the specifications of +class file and Java bytecode. For more details, read this book: + +<ul>Tim Lindholm and Frank Yellin, +"The Java Virtual Machine Specification 2nd Ed.", +Addison-Wesley, 1999. +</ul> + +</body> +</html> diff --git a/src/main/javassist/bytecode/stackmap/BasicBlock.java b/src/main/javassist/bytecode/stackmap/BasicBlock.java new file mode 100644 index 0000000..e5b64d3 --- /dev/null +++ b/src/main/javassist/bytecode/stackmap/BasicBlock.java @@ -0,0 +1,398 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.bytecode.stackmap; + +import javassist.bytecode.*; +import java.util.HashMap; +import java.util.ArrayList; + +/** + * A basic block is a sequence of bytecode that does not contain jump/branch + * instructions except at the last bytecode. + * Since Java6 or later does not allow JSR, this class deals with JSR as a + * non-branch instruction. + */ +public class BasicBlock { + public int position, length; + public int incoming; // the number of incoming branches. + public BasicBlock[] exit; // null if the block is a leaf. + public boolean stop; // true if the block ends with an unconditional jump. + public Catch toCatch; + + protected BasicBlock(int pos) { + position = pos; + length = 0; + incoming = 0; + } + + public static BasicBlock find(BasicBlock[] blocks, int pos) + throws BadBytecode + { + for (int i = 0; i < blocks.length; i++) { + int iPos = blocks[i].position; + if (iPos <= pos && pos < iPos + blocks[i].length) + return blocks[i]; + } + + throw new BadBytecode("no basic block at " + pos); + } + + public static class Catch { + Catch next; + BasicBlock body; + int typeIndex; + Catch(BasicBlock b, int i, Catch c) { + body = b; + typeIndex = i; + next = c; + } + } + + public String toString() { + StringBuffer sbuf = new StringBuffer(); + String cname = this.getClass().getName(); + int i = cname.lastIndexOf('.'); + sbuf.append(i < 0 ? cname : cname.substring(i + 1)); + sbuf.append("["); + toString2(sbuf); + sbuf.append("]"); + return sbuf.toString(); + } + + protected void toString2(StringBuffer sbuf) { + sbuf.append("pos=").append(position).append(", len=") + .append(length).append(", in=").append(incoming) + .append(", exit{"); + if (exit != null) { + for (int i = 0; i < exit.length; i++) + sbuf.append(exit[i].position).append(", "); + } + + sbuf.append("}, {"); + Catch th = toCatch; + while (th != null) { + sbuf.append("(").append(th.body.position).append(", ") + .append(th.typeIndex).append("), "); + th = th.next; + } + + sbuf.append("}"); + } + + static class Mark implements Comparable { + int position; + BasicBlock block; + BasicBlock[] jump; + boolean alwaysJmp; // true if a unconditional branch. + int size; // 0 unless the mark indicates RETURN etc. + Catch catcher; + + Mark(int p) { + position = p; + block = null; + jump = null; + alwaysJmp = false; + size = 0; + catcher = null; + } + + public int compareTo(Object obj) { + if (obj instanceof Mark) { + int pos = ((Mark)obj).position; + return position - pos; + } + + return -1; + } + + void setJump(BasicBlock[] bb, int s, boolean always) { + jump = bb; + size = s; + alwaysJmp = always; + } + } + + public static class Maker { + /* Override these two methods if a subclass of BasicBlock must be + * instantiated. + */ + protected BasicBlock makeBlock(int pos) { + return new BasicBlock(pos); + } + + protected BasicBlock[] makeArray(int size) { + return new BasicBlock[size]; + } + + private BasicBlock[] makeArray(BasicBlock b) { + BasicBlock[] array = makeArray(1); + array[0] = b; + return array; + } + + private BasicBlock[] makeArray(BasicBlock b1, BasicBlock b2) { + BasicBlock[] array = makeArray(2); + array[0] = b1; + array[1] = b2; + return array; + } + + public BasicBlock[] make(MethodInfo minfo) throws BadBytecode { + CodeAttribute ca = minfo.getCodeAttribute(); + if (ca == null) + return null; + + CodeIterator ci = ca.iterator(); + return make(ci, 0, ci.getCodeLength(), ca.getExceptionTable()); + } + + public BasicBlock[] make(CodeIterator ci, int begin, int end, + ExceptionTable et) + throws BadBytecode + { + HashMap marks = makeMarks(ci, begin, end, et); + BasicBlock[] bb = makeBlocks(marks); + addCatchers(bb, et); + return bb; + } + + /* Branch target + */ + private Mark makeMark(HashMap table, int pos) { + return makeMark0(table, pos, true, true); + } + + /* Branch instruction. + * size > 0 + */ + private Mark makeMark(HashMap table, int pos, BasicBlock[] jump, + int size, boolean always) { + Mark m = makeMark0(table, pos, false, false); + m.setJump(jump, size, always); + return m; + } + + private Mark makeMark0(HashMap table, int pos, + boolean isBlockBegin, boolean isTarget) { + Integer p = new Integer(pos); + Mark m = (Mark)table.get(p); + if (m == null) { + m = new Mark(pos); + table.put(p, m); + } + + if (isBlockBegin) { + if (m.block == null) + m.block = makeBlock(pos); + + if (isTarget) + m.block.incoming++; + } + + return m; + } + + private HashMap makeMarks(CodeIterator ci, int begin, int end, + ExceptionTable et) + throws BadBytecode + { + ci.begin(); + ci.move(begin); + HashMap marks = new HashMap(); + while (ci.hasNext()) { + int index = ci.next(); + if (index >= end) + break; + + int op = ci.byteAt(index); + if ((Opcode.IFEQ <= op && op <= Opcode.IF_ACMPNE) + || op == Opcode.IFNULL || op == Opcode.IFNONNULL) { + Mark to = makeMark(marks, index + ci.s16bitAt(index + 1)); + Mark next = makeMark(marks, index + 3); + makeMark(marks, index, makeArray(to.block, next.block), 3, false); + } + else if (Opcode.GOTO <= op && op <= Opcode.LOOKUPSWITCH) + switch (op) { + case Opcode.GOTO : + makeGoto(marks, index, index + ci.s16bitAt(index + 1), 3); + break; + case Opcode.JSR : + makeJsr(marks, index, index + ci.s16bitAt(index + 1), 3); + break; + case Opcode.RET : + makeMark(marks, index, null, 2, true); + break; + case Opcode.TABLESWITCH : { + int pos = (index & ~3) + 4; + int low = ci.s32bitAt(pos + 4); + int high = ci.s32bitAt(pos + 8); + int ncases = high - low + 1; + BasicBlock[] to = makeArray(ncases + 1); + to[0] = makeMark(marks, index + ci.s32bitAt(pos)).block; // default branch target + int p = pos + 12; + int n = p + ncases * 4; + int k = 1; + while (p < n) { + to[k++] = makeMark(marks, index + ci.s32bitAt(p)).block; + p += 4; + } + makeMark(marks, index, to, n - index, true); + break; } + case Opcode.LOOKUPSWITCH : { + int pos = (index & ~3) + 4; + int ncases = ci.s32bitAt(pos + 4); + BasicBlock[] to = makeArray(ncases + 1); + to[0] = makeMark(marks, index + ci.s32bitAt(pos)).block; // default branch target + int p = pos + 8 + 4; + int n = p + ncases * 8 - 4; + int k = 1; + while (p < n) { + to[k++] = makeMark(marks, index + ci.s32bitAt(p)).block; + p += 8; + } + makeMark(marks, index, to, n - index, true); + break; } + } + else if ((Opcode.IRETURN <= op && op <= Opcode.RETURN) || op == Opcode.ATHROW) + makeMark(marks, index, null, 1, true); + else if (op == Opcode.GOTO_W) + makeGoto(marks, index, index + ci.s32bitAt(index + 1), 5); + else if (op == Opcode.JSR_W) + makeJsr(marks, index, index + ci.s32bitAt(index + 1), 5); + else if (op == Opcode.WIDE && ci.byteAt(index + 1) == Opcode.RET) + makeMark(marks, index, null, 1, true); + } + + if (et != null) { + int i = et.size(); + while (--i >= 0) { + makeMark0(marks, et.startPc(i), true, false); + makeMark(marks, et.handlerPc(i)); + } + } + + return marks; + } + + private void makeGoto(HashMap marks, int pos, int target, int size) { + Mark to = makeMark(marks, target); + BasicBlock[] jumps = makeArray(to.block); + makeMark(marks, pos, jumps, size, true); + } + + /** + * We ignore JSR since Java 6 or later does not allow it. + */ + protected void makeJsr(HashMap marks, int pos, int target, int size) { + /* + Mark to = makeMark(marks, target); + Mark next = makeMark(marks, pos + size); + BasicBlock[] jumps = makeArray(to.block, next.block); + makeMark(marks, pos, jumps, size, false); + */ + } + + private BasicBlock[] makeBlocks(HashMap markTable) { + Mark[] marks = (Mark[])markTable.values() + .toArray(new Mark[markTable.size()]); + java.util.Arrays.sort(marks); + ArrayList blocks = new ArrayList(); + int i = 0; + BasicBlock prev; + if (marks.length > 0 && marks[0].position == 0 && marks[0].block != null) + prev = getBBlock(marks[i++]); + else + prev = makeBlock(0); + + blocks.add(prev); + while (i < marks.length) { + Mark m = marks[i++]; + BasicBlock bb = getBBlock(m); + if (bb == null) { + // the mark indicates a branch instruction + if (prev.length > 0) { + // the previous mark already has exits. + prev = makeBlock(prev.position + prev.length); + blocks.add(prev); + } + + prev.length = m.position + m.size - prev.position; + prev.exit = m.jump; + prev.stop = m.alwaysJmp; + } + else { + // the mark indicates a branch target + if (prev.length == 0) { + prev.length = m.position - prev.position; + bb.incoming++; + prev.exit = makeArray(bb); + } + else { + // the previous mark already has exits. + int prevPos = prev.position; + if (prevPos + prev.length < m.position) { + prev = makeBlock(prevPos + prev.length); + prev.length = m.position - prevPos; + // the incoming flow from dead code is not counted + // bb.incoming++; + prev.exit = makeArray(bb); + } + } + + blocks.add(bb); + prev = bb; + } + } + + return (BasicBlock[])blocks.toArray(makeArray(blocks.size())); + } + + private static BasicBlock getBBlock(Mark m) { + BasicBlock b = m.block; + if (b != null && m.size > 0) { + b.exit = m.jump; + b.length = m.size; + b.stop = m.alwaysJmp; + } + + return b; + } + + private void addCatchers(BasicBlock[] blocks, ExceptionTable et) + throws BadBytecode + { + if (et == null) + return; + + int i = et.size(); + while (--i >= 0) { + BasicBlock handler = find(blocks, et.handlerPc(i)); + int start = et.startPc(i); + int end = et.endPc(i); + int type = et.catchType(i); + handler.incoming--; + for (int k = 0; k < blocks.length; k++) { + BasicBlock bb = blocks[k]; + int iPos = bb.position; + if (start <= iPos && iPos < end) { + bb.toCatch = new Catch(handler, type, bb.toCatch); + handler.incoming++; + } + } + } + } + } +} diff --git a/src/main/javassist/bytecode/stackmap/Liveness.java b/src/main/javassist/bytecode/stackmap/Liveness.java new file mode 100644 index 0000000..4acd65e --- /dev/null +++ b/src/main/javassist/bytecode/stackmap/Liveness.java @@ -0,0 +1,365 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + + package javassist.bytecode.stackmap; + +import javassist.bytecode.*; + +public class Liveness { + protected static final byte UNKNOWN = 0; + protected static final byte READ = 1; + protected static final byte UPDATED = 2; + protected byte[] localsUsage; + + /** + * If true, all the arguments become alive within the whole method body. + * + * To correctly compute a stack map table, all the arguments must + * be alive (localsUsage[?] must be READ) at least in the first block. + */ + public static boolean useArgs = true; + + public void compute(CodeIterator ci, TypedBlock[] blocks, int maxLocals, + TypeData[] args) + throws BadBytecode + { + computeUsage(ci, blocks, maxLocals); + if (useArgs) + useAllArgs(blocks, args); + + computeLiveness1(blocks[0]); + while (hasChanged(blocks)) + computeLiveness2(blocks[0]); + } + + private void useAllArgs(TypedBlock[] blocks, TypeData[] args) { + for (int k = 0; k < blocks.length; k++) { + byte[] usage = blocks[k].localsUsage; + for (int i = 0; i < args.length; i++) + if (args[i] != TypeTag.TOP) + usage[i] = READ; + } + } + + static final int NOT_YET = 0; + static final int CHANGED_LAST = 1; + static final int DONE = 2; + static final int CHANGED_NOW = 3; + + private void computeLiveness1(TypedBlock tb) { + if (tb.updating) { + // a loop was detected. + computeLiveness1u(tb); + return; + } + + if (tb.inputs != null) + return; + + tb.updating = true; + byte[] usage = tb.localsUsage; + int n = usage.length; + boolean[] in = new boolean[n]; + for (int i = 0; i < n; i++) + in[i] = usage[i] == READ; + + BasicBlock.Catch handlers = tb.toCatch; + while (handlers != null) { + TypedBlock h = (TypedBlock)handlers.body; + computeLiveness1(h); + for (int k = 0; k < n; k++) + if (h.inputs[k]) + in[k] = true; + + handlers = handlers.next; + } + + if (tb.exit != null) { + for (int i = 0; i < tb.exit.length; i++) { + TypedBlock e = (TypedBlock)tb.exit[i]; + computeLiveness1(e); + for (int k = 0; k < n; k++) + if (!in[k]) + in[k] = usage[k] == UNKNOWN && e.inputs[k]; + } + } + + tb.updating = false; + if (tb.inputs == null) { + tb.inputs = in; + tb.status = DONE; + } + else { + for (int i = 0; i < n; i++) + if (in[i] && !tb.inputs[i]) { + tb.inputs[i] = true; + tb.status = CHANGED_NOW; + } + } + } + + private void computeLiveness1u(TypedBlock tb) { + if (tb.inputs == null) { + byte[] usage = tb.localsUsage; + int n = usage.length; + boolean[] in = new boolean[n]; + for (int i = 0; i < n; i++) + in[i] = usage[i] == READ; + + tb.inputs = in; + tb.status = DONE; + } + } + + private void computeLiveness2(TypedBlock tb) { + if (tb.updating || tb.status >= DONE) + return; + + tb.updating = true; + if (tb.exit == null) + tb.status = DONE; + else { + boolean changed = false; + for (int i = 0; i < tb.exit.length; i++) { + TypedBlock e = (TypedBlock)tb.exit[i]; + computeLiveness2(e); + if (e.status != DONE) + changed = true; + } + + if (changed) { + changed = false; + byte[] usage = tb.localsUsage; + int n = usage.length; + for (int i = 0; i < tb.exit.length; i++) { + TypedBlock e = (TypedBlock)tb.exit[i]; + if (e.status != DONE) + for (int k = 0; k < n; k++) + if (!tb.inputs[k]) { + if (usage[k] == UNKNOWN && e.inputs[k]) { + tb.inputs[k] = true; + changed = true; + } + } + } + + tb.status = changed ? CHANGED_NOW : DONE; + } + else + tb.status = DONE; + } + + if (computeLiveness2except(tb)) + tb.status = CHANGED_NOW; + + tb.updating = false; + } + + private boolean computeLiveness2except(TypedBlock tb) { + BasicBlock.Catch handlers = tb.toCatch; + boolean changed = false; + while (handlers != null) { + TypedBlock h = (TypedBlock)handlers.body; + computeLiveness2(h); + if (h.status != DONE) { + boolean[] in = tb.inputs; + int n = in.length; + for (int k = 0; k < n; k++) + if (!in[k] && h.inputs[k]) { + in[k] = true; + changed = true; + } + } + + handlers = handlers.next; + } + + return changed; + } + + private boolean hasChanged(TypedBlock[] blocks) { + int n = blocks.length; + boolean changed = false; + for (int i = 0; i < n; i++) { + TypedBlock tb = blocks[i]; + if (tb.status == CHANGED_NOW) { + tb.status = CHANGED_LAST; + changed = true; + } + else + tb.status = NOT_YET; + } + + return changed; + } + + private void computeUsage(CodeIterator ci, TypedBlock[] blocks, int maxLocals) + throws BadBytecode + { + int n = blocks.length; + for (int i = 0; i < n; i++) { + TypedBlock tb = blocks[i]; + localsUsage = tb.localsUsage = new byte[maxLocals]; + int pos = tb.position; + analyze(ci, pos, pos + tb.length); + localsUsage = null; + } + } + + protected final void readLocal(int reg) { + if (localsUsage[reg] == UNKNOWN) + localsUsage[reg] = READ; + } + + protected final void writeLocal(int reg) { + if (localsUsage[reg] == UNKNOWN) + localsUsage[reg] = UPDATED; + } + + protected void analyze(CodeIterator ci, int begin, int end) + throws BadBytecode + { + ci.begin(); + ci.move(begin); + while (ci.hasNext()) { + int index = ci.next(); + if (index >= end) + break; + + int op = ci.byteAt(index); + if (op < 96) + if (op < 54) + doOpcode0_53(ci, index, op); + else + doOpcode54_95(ci, index, op); + else + if (op == Opcode.IINC) { + // this does not call writeLocal(). + readLocal(ci.byteAt(index + 1)); + } + else if (op == Opcode.WIDE) + doWIDE(ci, index); + } + } + + private void doOpcode0_53(CodeIterator ci, int pos, int op) { + switch (op) { + case Opcode.ILOAD : + case Opcode.LLOAD : + case Opcode.FLOAD : + case Opcode.DLOAD : + case Opcode.ALOAD : + readLocal(ci.byteAt(pos + 1)); + break; + case Opcode.ILOAD_0 : + case Opcode.ILOAD_1 : + case Opcode.ILOAD_2 : + case Opcode.ILOAD_3 : + readLocal(op - Opcode.ILOAD_0); + break; + case Opcode.LLOAD_0 : + case Opcode.LLOAD_1 : + case Opcode.LLOAD_2 : + case Opcode.LLOAD_3 : + readLocal(op - Opcode.LLOAD_0); + break; + case Opcode.FLOAD_0 : + case Opcode.FLOAD_1 : + case Opcode.FLOAD_2 : + case Opcode.FLOAD_3 : + readLocal(op - Opcode.FLOAD_0); + break; + case Opcode.DLOAD_0 : + case Opcode.DLOAD_1 : + case Opcode.DLOAD_2 : + case Opcode.DLOAD_3 : + readLocal(op - Opcode.DLOAD_0); + break; + case Opcode.ALOAD_0 : + case Opcode.ALOAD_1 : + case Opcode.ALOAD_2 : + case Opcode.ALOAD_3 : + readLocal(op - Opcode.ALOAD_0); + break; + } + } + + private void doOpcode54_95(CodeIterator ci, int pos, int op) { + switch (op) { + case Opcode.ISTORE : + case Opcode.LSTORE : + case Opcode.FSTORE : + case Opcode.DSTORE : + case Opcode.ASTORE : + writeLocal(ci.byteAt(pos + 1)); + break; + case Opcode.ISTORE_0 : + case Opcode.ISTORE_1 : + case Opcode.ISTORE_2 : + case Opcode.ISTORE_3 : + writeLocal(op - Opcode.ISTORE_0); + break; + case Opcode.LSTORE_0 : + case Opcode.LSTORE_1 : + case Opcode.LSTORE_2 : + case Opcode.LSTORE_3 : + writeLocal(op - Opcode.LSTORE_0); + break; + case Opcode.FSTORE_0 : + case Opcode.FSTORE_1 : + case Opcode.FSTORE_2 : + case Opcode.FSTORE_3 : + writeLocal(op - Opcode.FSTORE_0); + break; + case Opcode.DSTORE_0 : + case Opcode.DSTORE_1 : + case Opcode.DSTORE_2 : + case Opcode.DSTORE_3 : + writeLocal(op - Opcode.DSTORE_0); + break; + case Opcode.ASTORE_0 : + case Opcode.ASTORE_1 : + case Opcode.ASTORE_2 : + case Opcode.ASTORE_3 : + writeLocal(op - Opcode.ASTORE_0); + break; + } + } + + private void doWIDE(CodeIterator ci, int pos) throws BadBytecode { + int op = ci.byteAt(pos + 1); + int var = ci.u16bitAt(pos + 2); + switch (op) { + case Opcode.ILOAD : + case Opcode.LLOAD : + case Opcode.FLOAD : + case Opcode.DLOAD : + case Opcode.ALOAD : + readLocal(var); + break; + case Opcode.ISTORE : + case Opcode.LSTORE : + case Opcode.FSTORE : + case Opcode.DSTORE : + case Opcode.ASTORE : + writeLocal(var); + break; + case Opcode.IINC : + readLocal(var); + // this does not call writeLocal(). + break; + } + } +} diff --git a/src/main/javassist/bytecode/stackmap/MapMaker.java b/src/main/javassist/bytecode/stackmap/MapMaker.java new file mode 100644 index 0000000..92cd37c --- /dev/null +++ b/src/main/javassist/bytecode/stackmap/MapMaker.java @@ -0,0 +1,526 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.bytecode.stackmap; + +import javassist.ClassPool; +import javassist.bytecode.*; + +/** + * Stack map maker. + */ +public class MapMaker extends Tracer { + /* + public static void main(String[] args) throws Exception { + boolean useMain2 = args[0].equals("0"); + if (useMain2 && args.length > 1) { + main2(args); + return; + } + + for (int i = 0; i < args.length; i++) + main1(args[i]); + } + + public static void main1(String className) throws Exception { + ClassPool cp = ClassPool.getDefault(); + //javassist.CtClass cc = cp.get(className); + javassist.CtClass cc = cp.makeClass(new java.io.FileInputStream(className)); + System.out.println(className); + ClassFile cf = cc.getClassFile(); + java.util.List minfos = cf.getMethods(); + for (int i = 0; i < minfos.size(); i++) { + MethodInfo minfo = (MethodInfo)minfos.get(i); + CodeAttribute ca = minfo.getCodeAttribute(); + if (ca != null) + ca.setAttribute(make(cp, minfo)); + } + + cc.writeFile("tmp"); + } + + public static void main2(String[] args) throws Exception { + ClassPool cp = ClassPool.getDefault(); + //javassist.CtClass cc = cp.get(args[1]); + javassist.CtClass cc = cp.makeClass(new java.io.FileInputStream(args[1])); + MethodInfo minfo; + if (args[2].equals("_init_")) + minfo = cc.getDeclaredConstructors()[0].getMethodInfo(); + // minfo = cc.getClassInitializer().getMethodInfo(); + else + minfo = cc.getDeclaredMethod(args[2]).getMethodInfo(); + + CodeAttribute ca = minfo.getCodeAttribute(); + if (ca == null) { + System.out.println("abstarct method"); + return; + } + + TypedBlock[] blocks = TypedBlock.makeBlocks(minfo, ca, false); + MapMaker mm = new MapMaker(cp, minfo, ca); + mm.make(blocks, ca.getCode()); + for (int i = 0; i < blocks.length; i++) + System.out.println(blocks[i]); + } + */ + + /** + * Computes the stack map table of the given method and returns it. + * It returns null if the given method does not have to have a + * stack map table. + */ + public static StackMapTable make(ClassPool classes, MethodInfo minfo) + throws BadBytecode + { + CodeAttribute ca = minfo.getCodeAttribute(); + if (ca == null) + return null; + + TypedBlock[] blocks = TypedBlock.makeBlocks(minfo, ca, true); + if (blocks == null) + return null; + + MapMaker mm = new MapMaker(classes, minfo, ca); + mm.make(blocks, ca.getCode()); + return mm.toStackMap(blocks); + } + + /** + * Computes the stack map table for J2ME. + * It returns null if the given method does not have to have a + * stack map table. + */ + public static StackMap make2(ClassPool classes, MethodInfo minfo) + throws BadBytecode + { + CodeAttribute ca = minfo.getCodeAttribute(); + if (ca == null) + return null; + + TypedBlock[] blocks = TypedBlock.makeBlocks(minfo, ca, true); + if (blocks == null) + return null; + + MapMaker mm = new MapMaker(classes, minfo, ca); + mm.make(blocks, ca.getCode()); + return mm.toStackMap2(minfo.getConstPool(), blocks); + } + + public MapMaker(ClassPool classes, MethodInfo minfo, CodeAttribute ca) { + super(classes, minfo.getConstPool(), + ca.getMaxStack(), ca.getMaxLocals(), + TypedBlock.getRetType(minfo.getDescriptor())); + } + + protected MapMaker(MapMaker old, boolean copyStack) { + super(old, copyStack); + } + + /** + * Runs an analyzer (Phase 1 and 2). + */ + void make(TypedBlock[] blocks, byte[] code) + throws BadBytecode + { + TypedBlock first = blocks[0]; + fixParamTypes(first); + TypeData[] srcTypes = first.localsTypes; + copyFrom(srcTypes.length, srcTypes, this.localsTypes); + make(code, first); + + int n = blocks.length; + for (int i = 0; i < n; i++) + evalExpected(blocks[i]); + } + + /* + * If a parameter type is String but it is used only as Object + * within the method body, this MapMaker class will report its type + * is Object. To avoid this, fixParamTypes calls TypeData.setType() + * on each parameter type. + */ + private void fixParamTypes(TypedBlock first) throws BadBytecode { + TypeData[] types = first.localsTypes; + int n = types.length; + for (int i = 0; i < n; i++) { + TypeData t = types[i]; + if (t instanceof TypeData.ClassName) { + /* Skip the following statement if t.isNullType() is true + * although a parameter type is never null type. + */ + TypeData.setType(t, t.getName(), classPool); + } + } + } + + // Phase 1 + + private void make(byte[] code, TypedBlock tb) + throws BadBytecode + { + BasicBlock.Catch handlers = tb.toCatch; + while (handlers != null) { + traceException(code, handlers); + handlers = handlers.next; + } + + int pos = tb.position; + int end = pos + tb.length; + while (pos < end) + pos += doOpcode(pos, code); + + if (tb.exit != null) { + for (int i = 0; i < tb.exit.length; i++) { + TypedBlock e = (TypedBlock)tb.exit[i]; + if (e.alreadySet()) + mergeMap(e, true); + else { + recordStackMap(e); + MapMaker maker = new MapMaker(this, true); + maker.make(code, e); + } + } + } + } + + private void traceException(byte[] code, TypedBlock.Catch handler) + throws BadBytecode + { + TypedBlock tb = (TypedBlock)handler.body; + if (tb.alreadySet()) + mergeMap(tb, false); + else { + recordStackMap(tb, handler.typeIndex); + MapMaker maker = new MapMaker(this, false); + + /* the following code is equivalent to maker.copyFrom(this) + * except stackTypes are not copied. + */ + maker.stackTypes[0] = tb.stackTypes[0].getSelf(); + maker.stackTop = 1; + maker.make(code, tb); + } + } + + private void mergeMap(TypedBlock dest, boolean mergeStack) { + boolean[] inputs = dest.inputs; + int n = inputs.length; + for (int i = 0; i < n; i++) + if (inputs[i]) + merge(localsTypes[i], dest.localsTypes[i]); + + if (mergeStack) { + n = stackTop; + for (int i = 0; i < n; i++) + merge(stackTypes[i], dest.stackTypes[i]); + } + } + + private void merge(TypeData td, TypeData target) { + boolean tdIsObj = false; + boolean targetIsObj = false; + // td or target is null if it is TOP. + if (td != TOP && td.isObjectType()) + tdIsObj = true; + + if (target != TOP && target.isObjectType()) + targetIsObj = true; + + if (tdIsObj && targetIsObj) + target.merge(td); + } + + private void recordStackMap(TypedBlock target) + throws BadBytecode + { + TypeData[] tStackTypes = new TypeData[stackTypes.length]; + int st = stackTop; + copyFrom(st, stackTypes, tStackTypes); + recordStackMap0(target, st, tStackTypes); + } + + private void recordStackMap(TypedBlock target, int exceptionType) + throws BadBytecode + { + String type; + if (exceptionType == 0) + type = "java.lang.Throwable"; + else + type = cpool.getClassInfo(exceptionType); + + TypeData[] tStackTypes = new TypeData[stackTypes.length]; + tStackTypes[0] = new TypeData.ClassName(type); + + recordStackMap0(target, 1, tStackTypes); + } + + private void recordStackMap0(TypedBlock target, int st, TypeData[] tStackTypes) + throws BadBytecode + { + int n = localsTypes.length; + TypeData[] tLocalsTypes = new TypeData[n]; + int k = copyFrom(n, localsTypes, tLocalsTypes); + + boolean[] inputs = target.inputs; + for (int i = 0; i < n; i++) + if (!inputs[i]) + tLocalsTypes[i] = TOP; + + target.setStackMap(st, tStackTypes, k, tLocalsTypes); + } + + // Phase 2 + + void evalExpected(TypedBlock target) throws BadBytecode { + ClassPool cp = classPool; + evalExpected(cp, target.stackTop, target.stackTypes); + TypeData[] types = target.localsTypes; + if (types != null) // unless this block is dead code + evalExpected(cp, types.length, types); + } + + private static void evalExpected(ClassPool cp, int n, TypeData[] types) + throws BadBytecode + { + for (int i = 0; i < n; i++) { + TypeData td = types[i]; + if (td != null) + td.evalExpectedType(cp); + } + } + + // Phase 3 + + public StackMapTable toStackMap(TypedBlock[] blocks) { + StackMapTable.Writer writer = new StackMapTable.Writer(32); + int n = blocks.length; + TypedBlock prev = blocks[0]; + int offsetDelta = prev.length; + if (prev.incoming > 0) { // the first instruction is a branch target. + writer.sameFrame(0); + offsetDelta--; + } + + for (int i = 1; i < n; i++) { + TypedBlock bb = blocks[i]; + if (isTarget(bb, blocks[i - 1])) { + bb.resetNumLocals(); + int diffL = stackMapDiff(prev.numLocals, prev.localsTypes, + bb.numLocals, bb.localsTypes); + toStackMapBody(writer, bb, diffL, offsetDelta, prev); + offsetDelta = bb.length - 1; + prev = bb; + } + else + offsetDelta += bb.length; + } + + return writer.toStackMapTable(cpool); + } + + /** + * Returns true if cur is a branch target. + */ + private boolean isTarget(TypedBlock cur, TypedBlock prev) { + int in = cur.incoming; + if (in > 1) + return true; + else if (in < 1) + return false; + + return prev.stop; + } + + private void toStackMapBody(StackMapTable.Writer writer, TypedBlock bb, + int diffL, int offsetDelta, TypedBlock prev) { + // if diffL is -100, two TypeData arrays do not share + // any elements. + + int stackTop = bb.stackTop; + if (stackTop == 0) { + if (diffL == 0) { + writer.sameFrame(offsetDelta); + return; + } + else if (0 > diffL && diffL >= -3) { + writer.chopFrame(offsetDelta, -diffL); + return; + } + else if (0 < diffL && diffL <= 3) { + int[] data = new int[diffL]; + int[] tags = fillStackMap(bb.numLocals - prev.numLocals, + prev.numLocals, data, + bb.localsTypes); + writer.appendFrame(offsetDelta, tags, data); + return; + } + } + else if (stackTop == 1 && diffL == 0) { + TypeData td = bb.stackTypes[0]; + if (td == TOP) + writer.sameLocals(offsetDelta, StackMapTable.TOP, 0); + else + writer.sameLocals(offsetDelta, td.getTypeTag(), + td.getTypeData(cpool)); + return; + } + else if (stackTop == 2 && diffL == 0) { + TypeData td = bb.stackTypes[0]; + if (td != TOP && td.is2WordType()) { + // bb.stackTypes[1] must be TOP. + writer.sameLocals(offsetDelta, td.getTypeTag(), + td.getTypeData(cpool)); + return; + } + } + + int[] sdata = new int[stackTop]; + int[] stags = fillStackMap(stackTop, 0, sdata, bb.stackTypes); + int[] ldata = new int[bb.numLocals]; + int[] ltags = fillStackMap(bb.numLocals, 0, ldata, bb.localsTypes); + writer.fullFrame(offsetDelta, ltags, ldata, stags, sdata); + } + + private int[] fillStackMap(int num, int offset, int[] data, TypeData[] types) { + int realNum = diffSize(types, offset, offset + num); + ConstPool cp = cpool; + int[] tags = new int[realNum]; + int j = 0; + for (int i = 0; i < num; i++) { + TypeData td = types[offset + i]; + if (td == TOP) { + tags[j] = StackMapTable.TOP; + data[j] = 0; + } + else { + tags[j] = td.getTypeTag(); + data[j] = td.getTypeData(cp); + if (td.is2WordType()) + i++; + } + + j++; + } + + return tags; + } + + private static int stackMapDiff(int oldTdLen, TypeData[] oldTd, + int newTdLen, TypeData[] newTd) + { + int diff = newTdLen - oldTdLen; + int len; + if (diff > 0) + len = oldTdLen; + else + len = newTdLen; + + if (stackMapEq(oldTd, newTd, len)) + if (diff > 0) + return diffSize(newTd, len, newTdLen); + else + return -diffSize(oldTd, len, oldTdLen); + else + return -100; + } + + private static boolean stackMapEq(TypeData[] oldTd, TypeData[] newTd, int len) { + for (int i = 0; i < len; i++) { + TypeData td = oldTd[i]; + if (td == TOP) { // the next element to LONG/DOUBLE is TOP. + if (newTd[i] != TOP) + return false; + } + else + if (!oldTd[i].equals(newTd[i])) + return false; + } + + return true; + } + + private static int diffSize(TypeData[] types, int offset, int len) { + int num = 0; + while (offset < len) { + TypeData td = types[offset++]; + num++; + if (td != TOP && td.is2WordType()) + offset++; + } + + return num; + } + + // Phase 3 for J2ME + + public StackMap toStackMap2(ConstPool cp, TypedBlock[] blocks) { + StackMap.Writer writer = new StackMap.Writer(); + int n = blocks.length; // should be > 0 + boolean[] effective = new boolean[n]; + TypedBlock prev = blocks[0]; + + // Is the first instruction a branch target? + effective[0] = prev.incoming > 0; + + int num = effective[0] ? 1 : 0; + for (int i = 1; i < n; i++) { + TypedBlock bb = blocks[i]; + if (effective[i] = isTarget(bb, blocks[i - 1])) { + bb.resetNumLocals(); + prev = bb; + num++; + } + } + + if (num == 0) + return null; + + writer.write16bit(num); + for (int i = 0; i < n; i++) + if (effective[i]) + writeStackFrame(writer, cp, blocks[i].position, blocks[i]); + + return writer.toStackMap(cp); + } + + private void writeStackFrame(StackMap.Writer writer, ConstPool cp, int offset, TypedBlock tb) { + writer.write16bit(offset); + writeVerifyTypeInfo(writer, cp, tb.localsTypes, tb.numLocals); + writeVerifyTypeInfo(writer, cp, tb.stackTypes, tb.stackTop); + } + + private void writeVerifyTypeInfo(StackMap.Writer writer, ConstPool cp, TypeData[] types, int num) { + int numDWord = 0; + for (int i = 0; i < num; i++) { + TypeData td = types[i]; + if (td != null && td.is2WordType()) { + numDWord++; + i++; + } + } + + writer.write16bit(num - numDWord); + for (int i = 0; i < num; i++) { + TypeData td = types[i]; + if (td == TOP) + writer.writeVerifyTypeInfo(StackMap.TOP, 0); + else { + writer.writeVerifyTypeInfo(td.getTypeTag(), td.getTypeData(cp)); + if (td.is2WordType()) + i++; + } + } + } +} diff --git a/src/main/javassist/bytecode/stackmap/Tracer.java b/src/main/javassist/bytecode/stackmap/Tracer.java new file mode 100644 index 0000000..89e788d --- /dev/null +++ b/src/main/javassist/bytecode/stackmap/Tracer.java @@ -0,0 +1,920 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.bytecode.stackmap; + +import javassist.bytecode.ByteArray; +import javassist.bytecode.Opcode; +import javassist.bytecode.ConstPool; +import javassist.bytecode.Descriptor; +import javassist.bytecode.BadBytecode; +import javassist.ClassPool; + +/* + * A class for performing abstract interpretation. + * See also MapMaker class. + */ + +public abstract class Tracer implements TypeTag { + protected ClassPool classPool; + protected ConstPool cpool; + protected String returnType; + + protected int stackTop; + protected TypeData[] stackTypes; + protected TypeData[] localsTypes; + + public Tracer(ClassPool classes, ConstPool cp, int maxStack, int maxLocals, + String retType) { + classPool = classes; + cpool = cp; + returnType = retType; + stackTop = 0; + stackTypes = new TypeData[maxStack]; + localsTypes = new TypeData[maxLocals]; + } + + public Tracer(Tracer t, boolean copyStack) { + classPool = t.classPool; + cpool = t.cpool; + returnType = t.returnType; + + stackTop = t.stackTop; + int size = t.stackTypes.length; + stackTypes = new TypeData[size]; + if (copyStack) + copyFrom(t.stackTop, t.stackTypes, stackTypes); + + int size2 = t.localsTypes.length; + localsTypes = new TypeData[size2]; + copyFrom(size2, t.localsTypes, localsTypes); + } + + protected static int copyFrom(int n, TypeData[] srcTypes, TypeData[] destTypes) { + int k = -1; + for (int i = 0; i < n; i++) { + TypeData t = srcTypes[i]; + destTypes[i] = t == TOP ? TOP : t.getSelf(); + if (t != TOP) + if (t.is2WordType()) + k = i + 1; + else + k = i; + } + + return k + 1; + } + + /** + * Does abstract interpretation on the given bytecode instruction. + * It records whether or not a local variable (i.e. register) is accessed. + * If the instruction requires that a local variable or + * a stack element has a more specific type, this method updates the + * type of it. + * + * @param pos the position of the instruction. + * @return the size of the instruction at POS. + */ + protected int doOpcode(int pos, byte[] code) throws BadBytecode { + try { + int op = code[pos] & 0xff; + if (op < 96) + if (op < 54) + return doOpcode0_53(pos, code, op); + else + return doOpcode54_95(pos, code, op); + else + if (op < 148) + return doOpcode96_147(pos, code, op); + else + return doOpcode148_201(pos, code, op); + } + catch (ArrayIndexOutOfBoundsException e) { + throw new BadBytecode("inconsistent stack height " + e.getMessage()); + } + } + + protected void visitBranch(int pos, byte[] code, int offset) throws BadBytecode {} + protected void visitGoto(int pos, byte[] code, int offset) throws BadBytecode {} + protected void visitReturn(int pos, byte[] code) throws BadBytecode {} + protected void visitThrow(int pos, byte[] code) throws BadBytecode {} + + /** + * @param pos the position of TABLESWITCH + * @param code bytecode + * @param n the number of case labels + * @param offsetPos the position of the branch-target table. + * @param defaultOffset the offset to the default branch target. + */ + protected void visitTableSwitch(int pos, byte[] code, int n, + int offsetPos, int defaultOffset) throws BadBytecode {} + + /** + * @param pos the position of LOOKUPSWITCH + * @param code bytecode + * @param n the number of case labels + * @param offsetPos the position of the table of pairs of a value and a branch target. + * @param defaultOffset the offset to the default branch target. + */ + protected void visitLookupSwitch(int pos, byte[] code, int n, + int pairsPos, int defaultOffset) throws BadBytecode {} + + /** + * Invoked when the visited instruction is jsr. + * Java6 or later does not allow using RET. + */ + protected void visitJSR(int pos, byte[] code) throws BadBytecode { + /* Since JSR pushes a return address onto the operand stack, + * the stack map at the entry point of a subroutine is + * stackTypes resulting after executing the following code: + * + * stackTypes[stackTop++] = TOP; + */ + } + + /** + * Invoked when the visited instruction is ret or wide ret. + * Java6 or later does not allow using RET. + */ + protected void visitRET(int pos, byte[] code) throws BadBytecode {} + + private int doOpcode0_53(int pos, byte[] code, int op) throws BadBytecode { + int reg; + TypeData[] stackTypes = this.stackTypes; + switch (op) { + case Opcode.NOP : + break; + case Opcode.ACONST_NULL : + stackTypes[stackTop++] = new TypeData.NullType(); + break; + case Opcode.ICONST_M1 : + case Opcode.ICONST_0 : + case Opcode.ICONST_1 : + case Opcode.ICONST_2 : + case Opcode.ICONST_3 : + case Opcode.ICONST_4 : + case Opcode.ICONST_5 : + stackTypes[stackTop++] = INTEGER; + break; + case Opcode.LCONST_0 : + case Opcode.LCONST_1 : + stackTypes[stackTop++] = LONG; + stackTypes[stackTop++] = TOP; + break; + case Opcode.FCONST_0 : + case Opcode.FCONST_1 : + case Opcode.FCONST_2 : + stackTypes[stackTop++] = FLOAT; + break; + case Opcode.DCONST_0 : + case Opcode.DCONST_1 : + stackTypes[stackTop++] = DOUBLE; + stackTypes[stackTop++] = TOP; + break; + case Opcode.BIPUSH : + case Opcode.SIPUSH : + stackTypes[stackTop++] = INTEGER; + return op == Opcode.SIPUSH ? 3 : 2; + case Opcode.LDC : + doLDC(code[pos + 1] & 0xff); + return 2; + case Opcode.LDC_W : + case Opcode.LDC2_W : + doLDC(ByteArray.readU16bit(code, pos + 1)); + return 3; + case Opcode.ILOAD : + return doXLOAD(INTEGER, code, pos); + case Opcode.LLOAD : + return doXLOAD(LONG, code, pos); + case Opcode.FLOAD : + return doXLOAD(FLOAT, code, pos); + case Opcode.DLOAD : + return doXLOAD(DOUBLE, code, pos); + case Opcode.ALOAD : + return doALOAD(code[pos + 1] & 0xff); + case Opcode.ILOAD_0 : + case Opcode.ILOAD_1 : + case Opcode.ILOAD_2 : + case Opcode.ILOAD_3 : + stackTypes[stackTop++] = INTEGER; + break; + case Opcode.LLOAD_0 : + case Opcode.LLOAD_1 : + case Opcode.LLOAD_2 : + case Opcode.LLOAD_3 : + stackTypes[stackTop++] = LONG; + stackTypes[stackTop++] = TOP; + break; + case Opcode.FLOAD_0 : + case Opcode.FLOAD_1 : + case Opcode.FLOAD_2 : + case Opcode.FLOAD_3 : + stackTypes[stackTop++] = FLOAT; + break; + case Opcode.DLOAD_0 : + case Opcode.DLOAD_1 : + case Opcode.DLOAD_2 : + case Opcode.DLOAD_3 : + stackTypes[stackTop++] = DOUBLE; + stackTypes[stackTop++] = TOP; + break; + case Opcode.ALOAD_0 : + case Opcode.ALOAD_1 : + case Opcode.ALOAD_2 : + case Opcode.ALOAD_3 : + reg = op - Opcode.ALOAD_0; + stackTypes[stackTop++] = localsTypes[reg]; + break; + case Opcode.IALOAD : + stackTypes[--stackTop - 1] = INTEGER; + break; + case Opcode.LALOAD : + stackTypes[stackTop - 2] = LONG; + stackTypes[stackTop - 1] = TOP; + break; + case Opcode.FALOAD : + stackTypes[--stackTop - 1] = FLOAT; + break; + case Opcode.DALOAD : + stackTypes[stackTop - 2] = DOUBLE; + stackTypes[stackTop - 1] = TOP; + break; + case Opcode.AALOAD : { + int s = --stackTop - 1; + TypeData data = stackTypes[s]; + if (data == null || !data.isObjectType()) + throw new BadBytecode("bad AALOAD"); + else + stackTypes[s] = new TypeData.ArrayElement(data); + + break; } + case Opcode.BALOAD : + case Opcode.CALOAD : + case Opcode.SALOAD : + stackTypes[--stackTop - 1] = INTEGER; + break; + default : + throw new RuntimeException("fatal"); + } + + return 1; + } + + private void doLDC(int index) { + TypeData[] stackTypes = this.stackTypes; + int tag = cpool.getTag(index); + if (tag == ConstPool.CONST_String) + stackTypes[stackTop++] = new TypeData.ClassName("java.lang.String"); + else if (tag == ConstPool.CONST_Integer) + stackTypes[stackTop++] = INTEGER; + else if (tag == ConstPool.CONST_Float) + stackTypes[stackTop++] = FLOAT; + else if (tag == ConstPool.CONST_Long) { + stackTypes[stackTop++] = LONG; + stackTypes[stackTop++] = TOP; + } + else if (tag == ConstPool.CONST_Double) { + stackTypes[stackTop++] = DOUBLE; + stackTypes[stackTop++] = TOP; + } + else if (tag == ConstPool.CONST_Class) + stackTypes[stackTop++] = new TypeData.ClassName("java.lang.Class"); + else + throw new RuntimeException("bad LDC: " + tag); + } + + private int doXLOAD(TypeData type, byte[] code, int pos) { + int localVar = code[pos + 1] & 0xff; + return doXLOAD(localVar, type); + } + + private int doXLOAD(int localVar, TypeData type) { + stackTypes[stackTop++] = type; + if (type.is2WordType()) + stackTypes[stackTop++] = TOP; + + return 2; + } + + private int doALOAD(int localVar) { // int localVar, TypeData type) { + stackTypes[stackTop++] = localsTypes[localVar]; + return 2; + } + + private int doOpcode54_95(int pos, byte[] code, int op) throws BadBytecode { + TypeData[] localsTypes = this.localsTypes; + TypeData[] stackTypes = this.stackTypes; + switch (op) { + case Opcode.ISTORE : + return doXSTORE(pos, code, INTEGER); + case Opcode.LSTORE : + return doXSTORE(pos, code, LONG); + case Opcode.FSTORE : + return doXSTORE(pos, code, FLOAT); + case Opcode.DSTORE : + return doXSTORE(pos, code, DOUBLE); + case Opcode.ASTORE : + return doASTORE(code[pos + 1] & 0xff); + case Opcode.ISTORE_0 : + case Opcode.ISTORE_1 : + case Opcode.ISTORE_2 : + case Opcode.ISTORE_3 : + { int var = op - Opcode.ISTORE_0; + localsTypes[var] = INTEGER; + stackTop--; } + break; + case Opcode.LSTORE_0 : + case Opcode.LSTORE_1 : + case Opcode.LSTORE_2 : + case Opcode.LSTORE_3 : + { int var = op - Opcode.LSTORE_0; + localsTypes[var] = LONG; + localsTypes[var + 1] = TOP; + stackTop -= 2; } + break; + case Opcode.FSTORE_0 : + case Opcode.FSTORE_1 : + case Opcode.FSTORE_2 : + case Opcode.FSTORE_3 : + { int var = op - Opcode.FSTORE_0; + localsTypes[var] = FLOAT; + stackTop--; } + break; + case Opcode.DSTORE_0 : + case Opcode.DSTORE_1 : + case Opcode.DSTORE_2 : + case Opcode.DSTORE_3 : + { int var = op - Opcode.DSTORE_0; + localsTypes[var] = DOUBLE; + localsTypes[var + 1] = TOP; + stackTop -= 2; } + break; + case Opcode.ASTORE_0 : + case Opcode.ASTORE_1 : + case Opcode.ASTORE_2 : + case Opcode.ASTORE_3 : + { int var = op - Opcode.ASTORE_0; + doASTORE(var); + break; } + case Opcode.IASTORE : + case Opcode.LASTORE : + case Opcode.FASTORE : + case Opcode.DASTORE : + stackTop -= (op == Opcode.LASTORE || op == Opcode.DASTORE) ? 4 : 3; + break; + case Opcode.AASTORE : + TypeData.setType(stackTypes[stackTop - 1], + TypeData.ArrayElement.getElementType(stackTypes[stackTop - 3].getName()), + classPool); + stackTop -= 3; + break; + case Opcode.BASTORE : + case Opcode.CASTORE : + case Opcode.SASTORE : + stackTop -= 3; + break; + case Opcode.POP : + stackTop--; + break; + case Opcode.POP2 : + stackTop -= 2; + break; + case Opcode.DUP : { + int sp = stackTop; + stackTypes[sp] = stackTypes[sp - 1]; + stackTop = sp + 1; + break; } + case Opcode.DUP_X1 : + case Opcode.DUP_X2 : { + int len = op - Opcode.DUP_X1 + 2; + doDUP_XX(1, len); + int sp = stackTop; + stackTypes[sp - len] = stackTypes[sp]; + stackTop = sp + 1; + break; } + case Opcode.DUP2 : + doDUP_XX(2, 2); + stackTop += 2; + break; + case Opcode.DUP2_X1 : + case Opcode.DUP2_X2 : { + int len = op - Opcode.DUP2_X1 + 3; + doDUP_XX(2, len); + int sp = stackTop; + stackTypes[sp - len] = stackTypes[sp]; + stackTypes[sp - len + 1] = stackTypes[sp + 1]; + stackTop = sp + 2; + break; } + case Opcode.SWAP : { + int sp = stackTop - 1; + TypeData t = stackTypes[sp]; + stackTypes[sp] = stackTypes[sp - 1]; + stackTypes[sp - 1] = t; + break; } + default : + throw new RuntimeException("fatal"); + } + + return 1; + } + + private int doXSTORE(int pos, byte[] code, TypeData type) { + int index = code[pos + 1] & 0xff; + return doXSTORE(index, type); + } + + private int doXSTORE(int index, TypeData type) { + stackTop--; + localsTypes[index] = type; + if (type.is2WordType()) { + stackTop--; + localsTypes[index + 1] = TOP; + } + + return 2; + } + + private int doASTORE(int index) { + stackTop--; + // implicit upcast might be done. + localsTypes[index] = stackTypes[stackTop].copy(); + return 2; + } + + private void doDUP_XX(int delta, int len) { + TypeData types[] = stackTypes; + int sp = stackTop - 1; + int end = sp - len; + while (sp > end) { + types[sp + delta] = types[sp]; + sp--; + } + } + + private int doOpcode96_147(int pos, byte[] code, int op) { + if (op <= Opcode.LXOR) { // IADD...LXOR + stackTop += Opcode.STACK_GROW[op]; + return 1; + } + + switch (op) { + case Opcode.IINC : + // this does not call writeLocal(). + return 3; + case Opcode.I2L : + stackTypes[stackTop] = LONG; + stackTypes[stackTop - 1] = TOP; + stackTop++; + break; + case Opcode.I2F : + stackTypes[stackTop - 1] = FLOAT; + break; + case Opcode.I2D : + stackTypes[stackTop] = DOUBLE; + stackTypes[stackTop - 1] = TOP; + stackTop++; + break; + case Opcode.L2I : + stackTypes[--stackTop - 1] = INTEGER; + break; + case Opcode.L2F : + stackTypes[--stackTop - 1] = FLOAT; + break; + case Opcode.L2D : + stackTypes[stackTop - 1] = DOUBLE; + break; + case Opcode.F2I : + stackTypes[stackTop - 1] = INTEGER; + break; + case Opcode.F2L : + stackTypes[stackTop - 1] = TOP; + stackTypes[stackTop++] = LONG; + break; + case Opcode.F2D : + stackTypes[stackTop - 1] = TOP; + stackTypes[stackTop++] = DOUBLE; + break; + case Opcode.D2I : + stackTypes[--stackTop - 1] = INTEGER; + break; + case Opcode.D2L : + stackTypes[stackTop - 1] = LONG; + break; + case Opcode.D2F : + stackTypes[--stackTop - 1] = FLOAT; + break; + case Opcode.I2B : + case Opcode.I2C : + case Opcode.I2S : + break; + default : + throw new RuntimeException("fatal"); + } + + return 1; + } + + private int doOpcode148_201(int pos, byte[] code, int op) throws BadBytecode { + switch (op) { + case Opcode.LCMP : + stackTypes[stackTop - 4] = INTEGER; + stackTop -= 3; + break; + case Opcode.FCMPL : + case Opcode.FCMPG : + stackTypes[--stackTop - 1] = INTEGER; + break; + case Opcode.DCMPL : + case Opcode.DCMPG : + stackTypes[stackTop - 4] = INTEGER; + stackTop -= 3; + break; + case Opcode.IFEQ : + case Opcode.IFNE : + case Opcode.IFLT : + case Opcode.IFGE : + case Opcode.IFGT : + case Opcode.IFLE : + stackTop--; // branch + visitBranch(pos, code, ByteArray.readS16bit(code, pos + 1)); + return 3; + case Opcode.IF_ICMPEQ : + case Opcode.IF_ICMPNE : + case Opcode.IF_ICMPLT : + case Opcode.IF_ICMPGE : + case Opcode.IF_ICMPGT : + case Opcode.IF_ICMPLE : + case Opcode.IF_ACMPEQ : + case Opcode.IF_ACMPNE : + stackTop -= 2; // branch + visitBranch(pos, code, ByteArray.readS16bit(code, pos + 1)); + return 3; + case Opcode.GOTO : + visitGoto(pos, code, ByteArray.readS16bit(code, pos + 1)); + return 3; // branch + case Opcode.JSR : + visitJSR(pos, code); + return 3; // branch + case Opcode.RET : + visitRET(pos, code); + return 2; + case Opcode.TABLESWITCH : { + stackTop--; // branch + int pos2 = (pos & ~3) + 8; + int low = ByteArray.read32bit(code, pos2); + int high = ByteArray.read32bit(code, pos2 + 4); + int n = high - low + 1; + visitTableSwitch(pos, code, n, pos2 + 8, ByteArray.read32bit(code, pos2 - 4)); + return n * 4 + 16 - (pos & 3); } + case Opcode.LOOKUPSWITCH : { + stackTop--; // branch + int pos2 = (pos & ~3) + 8; + int n = ByteArray.read32bit(code, pos2); + visitLookupSwitch(pos, code, n, pos2 + 4, ByteArray.read32bit(code, pos2 - 4)); + return n * 8 + 12 - (pos & 3); } + case Opcode.IRETURN : + stackTop--; + visitReturn(pos, code); + break; + case Opcode.LRETURN : + stackTop -= 2; + visitReturn(pos, code); + break; + case Opcode.FRETURN : + stackTop--; + visitReturn(pos, code); + break; + case Opcode.DRETURN : + stackTop -= 2; + visitReturn(pos, code); + break; + case Opcode.ARETURN : + TypeData.setType(stackTypes[--stackTop], returnType, classPool); + visitReturn(pos, code); + break; + case Opcode.RETURN : + visitReturn(pos, code); + break; + case Opcode.GETSTATIC : + return doGetField(pos, code, false); + case Opcode.PUTSTATIC : + return doPutField(pos, code, false); + case Opcode.GETFIELD : + return doGetField(pos, code, true); + case Opcode.PUTFIELD : + return doPutField(pos, code, true); + case Opcode.INVOKEVIRTUAL : + case Opcode.INVOKESPECIAL : + return doInvokeMethod(pos, code, true); + case Opcode.INVOKESTATIC : + return doInvokeMethod(pos, code, false); + case Opcode.INVOKEINTERFACE : + return doInvokeIntfMethod(pos, code); + case 186 : + throw new RuntimeException("bad opcode 186"); + case Opcode.NEW : { + int i = ByteArray.readU16bit(code, pos + 1); + stackTypes[stackTop++] + = new TypeData.UninitData(pos, cpool.getClassInfo(i)); + return 3; } + case Opcode.NEWARRAY : + return doNEWARRAY(pos, code); + case Opcode.ANEWARRAY : { + int i = ByteArray.readU16bit(code, pos + 1); + String type = cpool.getClassInfo(i).replace('.', '/'); + if (type.charAt(0) == '[') + type = "[" + type; + else + type = "[L" + type + ";"; + + stackTypes[stackTop - 1] + = new TypeData.ClassName(type); + return 3; } + case Opcode.ARRAYLENGTH : + TypeData.setType(stackTypes[stackTop - 1], "[Ljava.lang.Object;", classPool); + stackTypes[stackTop - 1] = INTEGER; + break; + case Opcode.ATHROW : + TypeData.setType(stackTypes[--stackTop], "java.lang.Throwable", classPool); + visitThrow(pos, code); + break; + case Opcode.CHECKCAST : { + // TypeData.setType(stackTypes[stackTop - 1], "java.lang.Object", classPool); + int i = ByteArray.readU16bit(code, pos + 1); + stackTypes[stackTop - 1] = new TypeData.ClassName(cpool.getClassInfo(i)); + return 3; } + case Opcode.INSTANCEOF : + // TypeData.setType(stackTypes[stackTop - 1], "java.lang.Object", classPool); + stackTypes[stackTop - 1] = INTEGER; + return 3; + case Opcode.MONITORENTER : + case Opcode.MONITOREXIT : + stackTop--; + // TypeData.setType(stackTypes[stackTop], "java.lang.Object", classPool); + break; + case Opcode.WIDE : + return doWIDE(pos, code); + case Opcode.MULTIANEWARRAY : + return doMultiANewArray(pos, code); + case Opcode.IFNULL : + case Opcode.IFNONNULL : + stackTop--; // branch + visitBranch(pos, code, ByteArray.readS16bit(code, pos + 1)); + return 3; + case Opcode.GOTO_W : + visitGoto(pos, code, ByteArray.read32bit(code, pos + 1)); + return 5; // branch + case Opcode.JSR_W : + visitJSR(pos, code); + return 5; + } + return 1; + } + + private int doWIDE(int pos, byte[] code) throws BadBytecode { + int op = code[pos + 1] & 0xff; + switch (op) { + case Opcode.ILOAD : + doWIDE_XLOAD(pos, code, INTEGER); + break; + case Opcode.LLOAD : + doWIDE_XLOAD(pos, code, LONG); + break; + case Opcode.FLOAD : + doWIDE_XLOAD(pos, code, FLOAT); + break; + case Opcode.DLOAD : + doWIDE_XLOAD(pos, code, DOUBLE); + break; + case Opcode.ALOAD : { + int index = ByteArray.readU16bit(code, pos + 2); + doALOAD(index); + break; } + case Opcode.ISTORE : + doWIDE_STORE(pos, code, INTEGER); + break; + case Opcode.LSTORE : + doWIDE_STORE(pos, code, LONG); + break; + case Opcode.FSTORE : + doWIDE_STORE(pos, code, FLOAT); + break; + case Opcode.DSTORE : + doWIDE_STORE(pos, code, DOUBLE); + break; + case Opcode.ASTORE : { + int index = ByteArray.readU16bit(code, pos + 2); + doASTORE(index); + break; } + case Opcode.IINC : + // this does not call writeLocal(). + return 6; + case Opcode.RET : + visitRET(pos, code); + break; + default : + throw new RuntimeException("bad WIDE instruction: " + op); + } + + return 4; + } + + private void doWIDE_XLOAD(int pos, byte[] code, TypeData type) { + int index = ByteArray.readU16bit(code, pos + 2); + doXLOAD(index, type); + } + + private void doWIDE_STORE(int pos, byte[] code, TypeData type) { + int index = ByteArray.readU16bit(code, pos + 2); + doXSTORE(index, type); + } + + private int doPutField(int pos, byte[] code, boolean notStatic) throws BadBytecode { + int index = ByteArray.readU16bit(code, pos + 1); + String desc = cpool.getFieldrefType(index); + stackTop -= Descriptor.dataSize(desc); + char c = desc.charAt(0); + if (c == 'L') + TypeData.setType(stackTypes[stackTop], getFieldClassName(desc, 0), classPool); + else if (c == '[') + TypeData.setType(stackTypes[stackTop], desc, classPool); + + setFieldTarget(notStatic, index); + return 3; + } + + private int doGetField(int pos, byte[] code, boolean notStatic) throws BadBytecode { + int index = ByteArray.readU16bit(code, pos + 1); + setFieldTarget(notStatic, index); + String desc = cpool.getFieldrefType(index); + pushMemberType(desc); + return 3; + } + + private void setFieldTarget(boolean notStatic, int index) throws BadBytecode { + if (notStatic) { + String className = cpool.getFieldrefClassName(index); + TypeData.setType(stackTypes[--stackTop], className, classPool); + } + } + + private int doNEWARRAY(int pos, byte[] code) { + int s = stackTop - 1; + String type; + switch (code[pos + 1] & 0xff) { + case Opcode.T_BOOLEAN : + type = "[Z"; + break; + case Opcode.T_CHAR : + type = "[C"; + break; + case Opcode.T_FLOAT : + type = "[F"; + break; + case Opcode.T_DOUBLE : + type = "[D"; + break; + case Opcode.T_BYTE : + type = "[B"; + break; + case Opcode.T_SHORT : + type = "[S"; + break; + case Opcode.T_INT : + type = "[I"; + break; + case Opcode.T_LONG : + type = "[J"; + break; + default : + throw new RuntimeException("bad newarray"); + } + + stackTypes[s] = new TypeData.ClassName(type); + return 2; + } + + private int doMultiANewArray(int pos, byte[] code) { + int i = ByteArray.readU16bit(code, pos + 1); + int dim = code[pos + 3] & 0xff; + stackTop -= dim - 1; + + String type = cpool.getClassInfo(i).replace('.', '/'); + stackTypes[stackTop - 1] = new TypeData.ClassName(type); + return 4; + } + + private int doInvokeMethod(int pos, byte[] code, boolean notStatic) throws BadBytecode { + int i = ByteArray.readU16bit(code, pos + 1); + String desc = cpool.getMethodrefType(i); + checkParamTypes(desc, 1); + if (notStatic) { + String className = cpool.getMethodrefClassName(i); + TypeData.setType(stackTypes[--stackTop], className, classPool); + } + + pushMemberType(desc); + return 3; + } + + private int doInvokeIntfMethod(int pos, byte[] code) throws BadBytecode { + int i = ByteArray.readU16bit(code, pos + 1); + String desc = cpool.getInterfaceMethodrefType(i); + checkParamTypes(desc, 1); + String className = cpool.getInterfaceMethodrefClassName(i); + TypeData.setType(stackTypes[--stackTop], className, classPool); + pushMemberType(desc); + return 5; + } + + private void pushMemberType(String descriptor) { + int top = 0; + if (descriptor.charAt(0) == '(') { + top = descriptor.indexOf(')') + 1; + if (top < 1) + throw new IndexOutOfBoundsException("bad descriptor: " + + descriptor); + } + + TypeData[] types = stackTypes; + int index = stackTop; + switch (descriptor.charAt(top)) { + case '[' : + types[index] = new TypeData.ClassName(descriptor.substring(top)); + break; + case 'L' : + types[index] = new TypeData.ClassName(getFieldClassName(descriptor, top)); + break; + case 'J' : + types[index] = LONG; + types[index + 1] = TOP; + stackTop += 2; + return; + case 'F' : + types[index] = FLOAT; + break; + case 'D' : + types[index] = DOUBLE; + types[index + 1] = TOP; + stackTop += 2; + return; + case 'V' : + return; + default : // C, B, S, I, Z + types[index] = INTEGER; + break; + } + + stackTop++; + } + + private static String getFieldClassName(String desc, int index) { + return desc.substring(index + 1, desc.length() - 1).replace('/', '.'); + } + + private void checkParamTypes(String desc, int i) throws BadBytecode { + char c = desc.charAt(i); + if (c == ')') + return; + + int k = i; + boolean array = false; + while (c == '[') { + array = true; + c = desc.charAt(++k); + } + + if (c == 'L') { + k = desc.indexOf(';', k) + 1; + if (k <= 0) + throw new IndexOutOfBoundsException("bad descriptor"); + } + else + k++; + + checkParamTypes(desc, k); + if (!array && (c == 'J' || c == 'D')) + stackTop -= 2; + else + stackTop--; + + if (array) + TypeData.setType(stackTypes[stackTop], + desc.substring(i, k), classPool); + else if (c == 'L') + TypeData.setType(stackTypes[stackTop], + desc.substring(i + 1, k - 1).replace('/', '.'), classPool); + } +} diff --git a/src/main/javassist/bytecode/stackmap/TypeData.java b/src/main/javassist/bytecode/stackmap/TypeData.java new file mode 100644 index 0000000..f6c6c4e --- /dev/null +++ b/src/main/javassist/bytecode/stackmap/TypeData.java @@ -0,0 +1,509 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.bytecode.stackmap; + +import javassist.ClassPool; +import javassist.CtClass; +import javassist.NotFoundException; +import javassist.bytecode.ConstPool; +import javassist.bytecode.StackMapTable; +import javassist.bytecode.BadBytecode; +import java.util.ArrayList; + +public abstract class TypeData { + /* Memo: + * array type is a subtype of Cloneable and Serializable + */ + + protected TypeData() {} + + public abstract void merge(TypeData neighbor); + + /** + * Sets the type name of this object type. If the given type name is + * a subclass of the current type name, then the given name becomes + * the name of this object type. + * + * @param className dot-separated name unless the type is an array type. + */ + static void setType(TypeData td, String className, ClassPool cp) throws BadBytecode { + if (td == TypeTag.TOP) + throw new BadBytecode("unset variable"); + else + td.setType(className, cp); + } + + public abstract boolean equals(Object obj); + + public abstract int getTypeTag(); + public abstract int getTypeData(ConstPool cp); + + /* + * See UninitData.getSelf(). + */ + public TypeData getSelf() { return this; } + + /* An operand value is copied when it is stored in a + * local variable. + */ + public abstract TypeData copy(); + + public abstract boolean isObjectType(); + public boolean is2WordType() { return false; } + public boolean isNullType() { return false; } + + public abstract String getName() throws BadBytecode; + protected abstract void setType(String s, ClassPool cp) throws BadBytecode; + public abstract void evalExpectedType(ClassPool cp) throws BadBytecode; + public abstract String getExpected() throws BadBytecode; + + /** + * Primitive types. + */ + protected static class BasicType extends TypeData { + private String name; + private int typeTag; + + public BasicType(String type, int tag) { + name = type; + typeTag = tag; + } + + public void merge(TypeData neighbor) {} + + public boolean equals(Object obj) { + return this == obj; + } + + public int getTypeTag() { return typeTag; } + public int getTypeData(ConstPool cp) { return 0; } + + public boolean isObjectType() { return false; } + + public boolean is2WordType() { + return typeTag == StackMapTable.LONG + || typeTag == StackMapTable.DOUBLE; + } + + public TypeData copy() { + return this; + } + + public void evalExpectedType(ClassPool cp) throws BadBytecode {} + + public String getExpected() throws BadBytecode { + return name; + } + + public String getName() { + return name; + } + + protected void setType(String s, ClassPool cp) throws BadBytecode { + throw new BadBytecode("conflict: " + name + " and " + s); + } + + public String toString() { return name; } + } + + protected static abstract class TypeName extends TypeData { + protected ArrayList equivalences; + + protected String expectedName; + private CtClass cache; + private boolean evalDone; + + protected TypeName() { + equivalences = new ArrayList(); + equivalences.add(this); + expectedName = null; + cache = null; + evalDone = false; + } + + public void merge(TypeData neighbor) { + if (this == neighbor) + return; + + if (!(neighbor instanceof TypeName)) + return; // neighbor might be UninitData + + TypeName neighbor2 = (TypeName)neighbor; + ArrayList list = equivalences; + ArrayList list2 = neighbor2.equivalences; + if (list == list2) + return; + + int n = list2.size(); + for (int i = 0; i < n; i++) { + TypeName tn = (TypeName)list2.get(i); + add(list, tn); + tn.equivalences = list; + } + } + + private static void add(ArrayList list, TypeData td) { + int n = list.size(); + for (int i = 0; i < n; i++) + if (list.get(i) == td) + return; + + list.add(td); + } + + /* NullType overrides this method. + */ + public int getTypeTag() { return StackMapTable.OBJECT; } + + public int getTypeData(ConstPool cp) { + String type; + try { + type = getExpected(); + } catch (BadBytecode e) { + throw new RuntimeException("fatal error: ", e); + } + + return getTypeData2(cp, type); + } + + /* NullType overrides this method. + */ + protected int getTypeData2(ConstPool cp, String type) { + return cp.addClassInfo(type); + } + + public boolean equals(Object obj) { + if (obj instanceof TypeName) { + try { + TypeName tn = (TypeName)obj; + return getExpected().equals(tn.getExpected()); + } + catch (BadBytecode e) {} + } + + return false; + } + + public boolean isObjectType() { return true; } + + protected void setType(String typeName, ClassPool cp) throws BadBytecode { + if (update(cp, expectedName, typeName)) + expectedName = typeName; + } + + public void evalExpectedType(ClassPool cp) throws BadBytecode { + if (this.evalDone) + return; + + ArrayList equiv = this.equivalences; + int n = equiv.size(); + String name = evalExpectedType2(equiv, n); + if (name == null) { + name = this.expectedName; + for (int i = 0; i < n; i++) { + TypeData td = (TypeData)equiv.get(i); + if (td instanceof TypeName) { + TypeName tn = (TypeName)td; + if (update(cp, name, tn.expectedName)) + name = tn.expectedName; + } + } + } + + for (int i = 0; i < n; i++) { + TypeData td = (TypeData)equiv.get(i); + if (td instanceof TypeName) { + TypeName tn = (TypeName)td; + tn.expectedName = name; + tn.cache = null; + tn.evalDone = true; + } + } + } + + private String evalExpectedType2(ArrayList equiv, int n) throws BadBytecode { + String origName = null; + for (int i = 0; i < n; i++) { + TypeData td = (TypeData)equiv.get(i); + if (!td.isNullType()) + if (origName == null) + origName = td.getName(); + else if (!origName.equals(td.getName())) + return null; + } + + return origName; + } + + protected boolean isTypeName() { return true; } + + private boolean update(ClassPool cp, String oldName, String typeName) throws BadBytecode { + if (typeName == null) + return false; + else if (oldName == null) + return true; + else if (oldName.equals(typeName)) + return false; + else if (typeName.charAt(0) == '[' + && oldName.equals("[Ljava.lang.Object;")) { + /* this rule is not correct but Tracer class sets the type + of the operand of arraylength to java.lang.Object[]. + Thus, int[] etc. must be a subtype of java.lang.Object[]. + */ + return true; + } + + try { + if (cache == null) + cache = cp.get(oldName); + + CtClass cache2 = cp.get(typeName); + if (cache2.subtypeOf(cache)) { + cache = cache2; + return true; + } + else + return false; + } + catch (NotFoundException e) { + throw new BadBytecode("cannot find " + e.getMessage()); + } + } + + /* See also NullType.getExpected(). + */ + public String getExpected() throws BadBytecode { + ArrayList equiv = equivalences; + if (equiv.size() == 1) + return getName(); + else { + String en = expectedName; + if (en == null) + return "java.lang.Object"; + else + return en; + } + } + + public String toString() { + try { + String en = expectedName; + if (en != null) + return en; + + String name = getName(); + if (equivalences.size() == 1) + return name; + else + return name + "?"; + } + catch (BadBytecode e) { + return "<" + e.getMessage() + ">"; + } + } + } + + /** + * Type data for OBJECT. + */ + public static class ClassName extends TypeName { + private String name; // dot separated. null if this object is a copy of another. + + public ClassName(String n) { + name = n; + } + + public TypeData copy() { + return new ClassName(name); + } + + public String getName() { // never returns null. + return name; + } + } + + /** + * Type data for NULL or OBJECT. + * The types represented by the instances of this class are + * initially NULL but will be OBJECT. + */ + public static class NullType extends ClassName { + public NullType() { + super("null"); // type name + } + + public TypeData copy() { + return new NullType(); + } + + public boolean isNullType() { return true; } + + public int getTypeTag() { + try { + if ("null".equals(getExpected())) + return StackMapTable.NULL; + else + return super.getTypeTag(); + } + catch (BadBytecode e) { + throw new RuntimeException("fatal error: ", e); + } + } + + protected int getTypeData2(ConstPool cp, String type) { + if ("null".equals(type)) + return 0; + else + return super.getTypeData2(cp, type); + } + + public String getExpected() throws BadBytecode { + String en = expectedName; + if (en == null) { + // ArrayList equiv = equivalences; + // if (equiv.size() == 1) + // return getName(); + // else + return "java.lang.Object"; + } + else + return en; + } + } + + /** + * Type data for OBJECT if the type is an object type and is + * derived as an element type from an array type by AALOAD. + */ + public static class ArrayElement extends TypeName { + TypeData array; + + public ArrayElement(TypeData a) { // a is never null + array = a; + } + + public TypeData copy() { + return new ArrayElement(array); + } + + protected void setType(String typeName, ClassPool cp) throws BadBytecode { + super.setType(typeName, cp); + array.setType(getArrayType(typeName), cp); + } + + public String getName() throws BadBytecode { + String name = array.getName(); + if (name.length() > 1 && name.charAt(0) == '[') { + char c = name.charAt(1); + if (c == 'L') + return name.substring(2, name.length() - 1).replace('/', '.'); + else if (c == '[') + return name.substring(1); + } + + throw new BadBytecode("bad array type for AALOAD: " + + name); + } + + public static String getArrayType(String elementType) { + if (elementType.charAt(0) == '[') + return "[" + elementType; + else + return "[L" + elementType.replace('.', '/') + ";"; + } + + public static String getElementType(String arrayType) { + char c = arrayType.charAt(1); + if (c == 'L') + return arrayType.substring(2, arrayType.length() - 1).replace('/', '.'); + else if (c == '[') + return arrayType.substring(1); + else + return arrayType; + } + } + + /** + * Type data for UNINIT. + */ + public static class UninitData extends TypeData { + String className; + int offset; + boolean initialized; + + UninitData(int offset, String className) { + this.className = className; + this.offset = offset; + this.initialized = false; + } + + public void merge(TypeData neighbor) {} + + public int getTypeTag() { return StackMapTable.UNINIT; } + public int getTypeData(ConstPool cp) { return offset; } + + public boolean equals(Object obj) { + if (obj instanceof UninitData) { + UninitData ud = (UninitData)obj; + return offset == ud.offset && className.equals(ud.className); + } + else + return false; + } + + public TypeData getSelf() { + if (initialized) + return copy(); + else + return this; + } + + public TypeData copy() { + return new ClassName(className); + } + + public boolean isObjectType() { return true; } + + protected void setType(String typeName, ClassPool cp) throws BadBytecode { + initialized = true; + } + + public void evalExpectedType(ClassPool cp) throws BadBytecode {} + + public String getName() { + return className; + } + + public String getExpected() { return className; } + + public String toString() { return "uninit:" + className + "@" + offset; } + } + + public static class UninitThis extends UninitData { + UninitThis(String className) { + super(-1, className); + } + + public int getTypeTag() { return StackMapTable.THIS; } + public int getTypeData(ConstPool cp) { return 0; } + + public boolean equals(Object obj) { + return obj instanceof UninitThis; + } + + public String toString() { return "uninit:this"; } + } +} diff --git a/src/main/javassist/bytecode/stackmap/TypeTag.java b/src/main/javassist/bytecode/stackmap/TypeTag.java new file mode 100644 index 0000000..4172068 --- /dev/null +++ b/src/main/javassist/bytecode/stackmap/TypeTag.java @@ -0,0 +1,28 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.bytecode.stackmap; + +import javassist.bytecode.StackMapTable; + +public interface TypeTag { + TypeData TOP = null; + TypeData INTEGER = new TypeData.BasicType("int", StackMapTable.INTEGER); + TypeData FLOAT = new TypeData.BasicType("float", StackMapTable.FLOAT); + TypeData DOUBLE = new TypeData.BasicType("double", StackMapTable.DOUBLE); + TypeData LONG = new TypeData.BasicType("long", StackMapTable.LONG); + + // and NULL, THIS, OBJECT, UNINIT +} diff --git a/src/main/javassist/bytecode/stackmap/TypedBlock.java b/src/main/javassist/bytecode/stackmap/TypedBlock.java new file mode 100644 index 0000000..65dce97 --- /dev/null +++ b/src/main/javassist/bytecode/stackmap/TypedBlock.java @@ -0,0 +1,249 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.bytecode.stackmap; + +import javassist.bytecode.*; + +public class TypedBlock extends BasicBlock { + public int stackTop, numLocals; + public TypeData[] stackTypes, localsTypes; + + // set by a Liveness object. + // inputs[i] is true if the i-th variable is used within this block. + public boolean[] inputs; + + // working area for Liveness class. + public boolean updating; + public int status; + public byte[] localsUsage; + + /** + * Divides the method body into basic blocks. + * The type information of the first block is initialized. + * + * @param optmize if it is true and the method does not include + * branches, this method returns null. + */ + public static TypedBlock[] makeBlocks(MethodInfo minfo, CodeAttribute ca, + boolean optimize) + throws BadBytecode + { + TypedBlock[] blocks = (TypedBlock[])new Maker().make(minfo); + if (optimize && blocks.length < 2) + if (blocks.length == 0 || blocks[0].incoming == 0) + return null; + + ConstPool pool = minfo.getConstPool(); + boolean isStatic = (minfo.getAccessFlags() & AccessFlag.STATIC) != 0; + blocks[0].initFirstBlock(ca.getMaxStack(), ca.getMaxLocals(), + pool.getClassName(), minfo.getDescriptor(), + isStatic, minfo.isConstructor()); + new Liveness().compute(ca.iterator(), blocks, ca.getMaxLocals(), + blocks[0].localsTypes); + return blocks; + } + + protected TypedBlock(int pos) { + super(pos); + localsTypes = null; + inputs = null; + updating = false; + } + + protected void toString2(StringBuffer sbuf) { + super.toString2(sbuf); + sbuf.append(",\n stack={"); + printTypes(sbuf, stackTop, stackTypes); + sbuf.append("}, locals={"); + printTypes(sbuf, numLocals, localsTypes); + sbuf.append("}, inputs={"); + if (inputs != null) + for (int i = 0; i < inputs.length; i++) + sbuf.append(inputs[i] ? "1, " : "0, "); + + sbuf.append('}'); + } + + private void printTypes(StringBuffer sbuf, int size, + TypeData[] types) { + if (types == null) + return; + + for (int i = 0; i < size; i++) { + if (i > 0) + sbuf.append(", "); + + TypeData td = types[i]; + sbuf.append(td == null ? "<>" : td.toString()); + } + } + + public boolean alreadySet() { + return localsTypes != null; + } + + public void setStackMap(int st, TypeData[] stack, int nl, TypeData[] locals) + throws BadBytecode + { + stackTop = st; + stackTypes = stack; + numLocals = nl; + localsTypes = locals; + } + + /* + * Computes the correct value of numLocals. + */ + public void resetNumLocals() { + if (localsTypes != null) { + int nl = localsTypes.length; + while (nl > 0 && localsTypes[nl - 1] == TypeTag.TOP) { + if (nl > 1) { + TypeData td = localsTypes[nl - 2]; + if (td == TypeTag.LONG || td == TypeTag.DOUBLE) + break; + } + + --nl; + } + + numLocals = nl; + } + } + + public static class Maker extends BasicBlock.Maker { + protected BasicBlock makeBlock(int pos) { + return new TypedBlock(pos); + } + + protected BasicBlock[] makeArray(int size) { + return new TypedBlock[size]; + } + } + + /** + * Initializes the first block by the given method descriptor. + * + * @param block the first basic block that this method initializes. + * @param className a dot-separated fully qualified class name. + * For example, <code>javassist.bytecode.stackmap.BasicBlock</code>. + * @param methodDesc method descriptor. + * @param isStatic true if the method is a static method. + * @param isConstructor true if the method is a constructor. + */ + void initFirstBlock(int maxStack, int maxLocals, String className, + String methodDesc, boolean isStatic, boolean isConstructor) + throws BadBytecode + { + if (methodDesc.charAt(0) != '(') + throw new BadBytecode("no method descriptor: " + methodDesc); + + stackTop = 0; + stackTypes = new TypeData[maxStack]; + TypeData[] locals = new TypeData[maxLocals]; + if (isConstructor) + locals[0] = new TypeData.UninitThis(className); + else if (!isStatic) + locals[0] = new TypeData.ClassName(className); + + int n = isStatic ? -1 : 0; + int i = 1; + try { + while ((i = descToTag(methodDesc, i, ++n, locals)) > 0) + if (locals[n].is2WordType()) + locals[++n] = TypeTag.TOP; + } + catch (StringIndexOutOfBoundsException e) { + throw new BadBytecode("bad method descriptor: " + + methodDesc); + } + + numLocals = n; + localsTypes = locals; + } + + private static int descToTag(String desc, int i, + int n, TypeData[] types) + throws BadBytecode + { + int i0 = i; + int arrayDim = 0; + char c = desc.charAt(i); + if (c == ')') + return 0; + + while (c == '[') { + ++arrayDim; + c = desc.charAt(++i); + } + + if (c == 'L') { + int i2 = desc.indexOf(';', ++i); + if (arrayDim > 0) + types[n] = new TypeData.ClassName(desc.substring(i0, ++i2)); + else + types[n] = new TypeData.ClassName(desc.substring(i0 + 1, ++i2 - 1) + .replace('/', '.')); + return i2; + } + else if (arrayDim > 0) { + types[n] = new TypeData.ClassName(desc.substring(i0, ++i)); + return i; + } + else { + TypeData t = toPrimitiveTag(c); + if (t == null) + throw new BadBytecode("bad method descriptor: " + desc); + + types[n] = t; + return i + 1; + } + } + + private static TypeData toPrimitiveTag(char c) { + switch (c) { + case 'Z' : + case 'C' : + case 'B' : + case 'S' : + case 'I' : + return TypeTag.INTEGER; + case 'J' : + return TypeTag.LONG; + case 'F' : + return TypeTag.FLOAT; + case 'D' : + return TypeTag.DOUBLE; + case 'V' : + default : + return null; + } + } + + public static String getRetType(String desc) { + int i = desc.indexOf(')'); + if (i < 0) + return "java.lang.Object"; + + char c = desc.charAt(i + 1); + if (c == '[') + return desc.substring(i + 1); + else if (c == 'L') + return desc.substring(i + 2, desc.length() - 1).replace('/', '.'); + else + return "java.lang.Object"; + } +} diff --git a/src/main/javassist/compiler/AccessorMaker.java b/src/main/javassist/compiler/AccessorMaker.java new file mode 100644 index 0000000..7f4f918 --- /dev/null +++ b/src/main/javassist/compiler/AccessorMaker.java @@ -0,0 +1,259 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.compiler; + +import javassist.*; +import javassist.bytecode.*; +import java.util.HashMap; + +/** + * AccessorMaker maintains accessors to private members of an enclosing + * class. It is necessary for compiling a method in an inner class. + */ +public class AccessorMaker { + private CtClass clazz; + private int uniqueNumber; + private HashMap accessors; + + static final String lastParamType = "javassist.runtime.Inner"; + + public AccessorMaker(CtClass c) { + clazz = c; + uniqueNumber = 1; + accessors = new HashMap(); + } + + public String getConstructor(CtClass c, String desc, MethodInfo orig) + throws CompileError + { + String key = "<init>:" + desc; + String consDesc = (String)accessors.get(key); + if (consDesc != null) + return consDesc; // already exists. + + consDesc = Descriptor.appendParameter(lastParamType, desc); + ClassFile cf = clazz.getClassFile(); // turn on the modified flag. + try { + ConstPool cp = cf.getConstPool(); + ClassPool pool = clazz.getClassPool(); + MethodInfo minfo + = new MethodInfo(cp, MethodInfo.nameInit, consDesc); + minfo.setAccessFlags(0); + minfo.addAttribute(new SyntheticAttribute(cp)); + ExceptionsAttribute ea = orig.getExceptionsAttribute(); + if (ea != null) + minfo.addAttribute(ea.copy(cp, null)); + + CtClass[] params = Descriptor.getParameterTypes(desc, pool); + Bytecode code = new Bytecode(cp); + code.addAload(0); + int regno = 1; + for (int i = 0; i < params.length; ++i) + regno += code.addLoad(regno, params[i]); + code.setMaxLocals(regno + 1); // the last parameter is added. + code.addInvokespecial(clazz, MethodInfo.nameInit, desc); + + code.addReturn(null); + minfo.setCodeAttribute(code.toCodeAttribute()); + cf.addMethod(minfo); + } + catch (CannotCompileException e) { + throw new CompileError(e); + } + catch (NotFoundException e) { + throw new CompileError(e); + } + + accessors.put(key, consDesc); + return consDesc; + } + + /** + * Returns the name of the method for accessing a private method. + * + * @param name the name of the private method. + * @param desc the descriptor of the private method. + * @param accDesc the descriptor of the accessor method. The first + * parameter type is <code>clazz</code>. + * If the private method is static, + * <code>accDesc<code> must be identical to <code>desc</code>. + * + * @param orig the method info of the private method. + * @return + */ + public String getMethodAccessor(String name, String desc, String accDesc, + MethodInfo orig) + throws CompileError + { + String key = name + ":" + desc; + String accName = (String)accessors.get(key); + if (accName != null) + return accName; // already exists. + + ClassFile cf = clazz.getClassFile(); // turn on the modified flag. + accName = findAccessorName(cf); + try { + ConstPool cp = cf.getConstPool(); + ClassPool pool = clazz.getClassPool(); + MethodInfo minfo + = new MethodInfo(cp, accName, accDesc); + minfo.setAccessFlags(AccessFlag.STATIC); + minfo.addAttribute(new SyntheticAttribute(cp)); + ExceptionsAttribute ea = orig.getExceptionsAttribute(); + if (ea != null) + minfo.addAttribute(ea.copy(cp, null)); + + CtClass[] params = Descriptor.getParameterTypes(accDesc, pool); + int regno = 0; + Bytecode code = new Bytecode(cp); + for (int i = 0; i < params.length; ++i) + regno += code.addLoad(regno, params[i]); + + code.setMaxLocals(regno); + if (desc == accDesc) + code.addInvokestatic(clazz, name, desc); + else + code.addInvokevirtual(clazz, name, desc); + + code.addReturn(Descriptor.getReturnType(desc, pool)); + minfo.setCodeAttribute(code.toCodeAttribute()); + cf.addMethod(minfo); + } + catch (CannotCompileException e) { + throw new CompileError(e); + } + catch (NotFoundException e) { + throw new CompileError(e); + } + + accessors.put(key, accName); + return accName; + } + + /** + * Returns the method_info representing the added getter. + */ + public MethodInfo getFieldGetter(FieldInfo finfo, boolean is_static) + throws CompileError + { + String fieldName = finfo.getName(); + String key = fieldName + ":getter"; + Object res = accessors.get(key); + if (res != null) + return (MethodInfo)res; // already exists. + + ClassFile cf = clazz.getClassFile(); // turn on the modified flag. + String accName = findAccessorName(cf); + try { + ConstPool cp = cf.getConstPool(); + ClassPool pool = clazz.getClassPool(); + String fieldType = finfo.getDescriptor(); + String accDesc; + if (is_static) + accDesc = "()" + fieldType; + else + accDesc = "(" + Descriptor.of(clazz) + ")" + fieldType; + + MethodInfo minfo = new MethodInfo(cp, accName, accDesc); + minfo.setAccessFlags(AccessFlag.STATIC); + minfo.addAttribute(new SyntheticAttribute(cp)); + Bytecode code = new Bytecode(cp); + if (is_static) { + code.addGetstatic(Bytecode.THIS, fieldName, fieldType); + } + else { + code.addAload(0); + code.addGetfield(Bytecode.THIS, fieldName, fieldType); + code.setMaxLocals(1); + } + + code.addReturn(Descriptor.toCtClass(fieldType, pool)); + minfo.setCodeAttribute(code.toCodeAttribute()); + cf.addMethod(minfo); + accessors.put(key, minfo); + return minfo; + } + catch (CannotCompileException e) { + throw new CompileError(e); + } + catch (NotFoundException e) { + throw new CompileError(e); + } + } + + /** + * Returns the method_info representing the added setter. + */ + public MethodInfo getFieldSetter(FieldInfo finfo, boolean is_static) + throws CompileError + { + String fieldName = finfo.getName(); + String key = fieldName + ":setter"; + Object res = accessors.get(key); + if (res != null) + return (MethodInfo)res; // already exists. + + ClassFile cf = clazz.getClassFile(); // turn on the modified flag. + String accName = findAccessorName(cf); + try { + ConstPool cp = cf.getConstPool(); + ClassPool pool = clazz.getClassPool(); + String fieldType = finfo.getDescriptor(); + String accDesc; + if (is_static) + accDesc = "(" + fieldType + ")V"; + else + accDesc = "(" + Descriptor.of(clazz) + fieldType + ")V"; + + MethodInfo minfo = new MethodInfo(cp, accName, accDesc); + minfo.setAccessFlags(AccessFlag.STATIC); + minfo.addAttribute(new SyntheticAttribute(cp)); + Bytecode code = new Bytecode(cp); + int reg; + if (is_static) { + reg = code.addLoad(0, Descriptor.toCtClass(fieldType, pool)); + code.addPutstatic(Bytecode.THIS, fieldName, fieldType); + } + else { + code.addAload(0); + reg = code.addLoad(1, Descriptor.toCtClass(fieldType, pool)) + + 1; + code.addPutfield(Bytecode.THIS, fieldName, fieldType); + } + + code.addReturn(null); + code.setMaxLocals(reg); + minfo.setCodeAttribute(code.toCodeAttribute()); + cf.addMethod(minfo); + accessors.put(key, minfo); + return minfo; + } + catch (CannotCompileException e) { + throw new CompileError(e); + } + catch (NotFoundException e) { + throw new CompileError(e); + } + } + + private String findAccessorName(ClassFile cf) { + String accName; + do { + accName = "access$" + uniqueNumber++; + } while (cf.getMethod(accName) != null); + return accName; + } +} diff --git a/src/main/javassist/compiler/CodeGen.java b/src/main/javassist/compiler/CodeGen.java new file mode 100644 index 0000000..470a976 --- /dev/null +++ b/src/main/javassist/compiler/CodeGen.java @@ -0,0 +1,1921 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.compiler; + +import java.util.ArrayList; +import java.util.Arrays; +import javassist.compiler.ast.*; +import javassist.bytecode.*; + +/* The code generator is implemeted by three files: + * CodeGen.java, MemberCodeGen.java, and JvstCodeGen. + * I just wanted to split a big file into three smaller ones. + */ + +public abstract class CodeGen extends Visitor implements Opcode, TokenId { + static final String javaLangObject = "java.lang.Object"; + static final String jvmJavaLangObject = "java/lang/Object"; + + static final String javaLangString = "java.lang.String"; + static final String jvmJavaLangString = "java/lang/String"; + + protected Bytecode bytecode; + private int tempVar; + TypeChecker typeChecker; + + /** + * true if the last visited node is a return statement. + */ + protected boolean hasReturned; + + /** + * Must be true if compilation is for a static method. + */ + public boolean inStaticMethod; + + protected ArrayList breakList, continueList; + + /** + * doit() in ReturnHook is called from atReturn(). + */ + protected static abstract class ReturnHook { + ReturnHook next; + + /** + * Returns true if the generated code ends with return, + * throw, or goto. + */ + protected abstract boolean doit(Bytecode b, int opcode); + + protected ReturnHook(CodeGen gen) { + next = gen.returnHooks; + gen.returnHooks = this; + } + + protected void remove(CodeGen gen) { + gen.returnHooks = next; + } + } + + protected ReturnHook returnHooks; + + /* The following fields are used by atXXX() methods + * for returning the type of the compiled expression. + */ + protected int exprType; // VOID, NULL, CLASS, BOOLEAN, INT, ... + protected int arrayDim; + protected String className; // JVM-internal representation + + public CodeGen(Bytecode b) { + bytecode = b; + tempVar = -1; + typeChecker = null; + hasReturned = false; + inStaticMethod = false; + breakList = null; + continueList = null; + returnHooks = null; + } + + public void setTypeChecker(TypeChecker checker) { + typeChecker = checker; + } + + protected static void fatal() throws CompileError { + throw new CompileError("fatal"); + } + + public static boolean is2word(int type, int dim) { + return dim == 0 && (type == DOUBLE || type == LONG); + } + + public int getMaxLocals() { return bytecode.getMaxLocals(); } + + public void setMaxLocals(int n) { + bytecode.setMaxLocals(n); + } + + protected void incMaxLocals(int size) { + bytecode.incMaxLocals(size); + } + + /** + * Returns a local variable that single or double words can be + * stored in. + */ + protected int getTempVar() { + if (tempVar < 0) { + tempVar = getMaxLocals(); + incMaxLocals(2); + } + + return tempVar; + } + + protected int getLocalVar(Declarator d) { + int v = d.getLocalVar(); + if (v < 0) { + v = getMaxLocals(); // delayed variable allocation. + d.setLocalVar(v); + incMaxLocals(1); + } + + return v; + } + + /** + * Returns the JVM-internal representation of this class name. + */ + protected abstract String getThisName(); + + /** + * Returns the JVM-internal representation of this super class name. + */ + protected abstract String getSuperName() throws CompileError; + + /* Converts a class name into a JVM-internal representation. + * + * It may also expand a simple class name to java.lang.*. + * For example, this converts Object into java/lang/Object. + */ + protected abstract String resolveClassName(ASTList name) + throws CompileError; + + /* Expands a simple class name to java.lang.*. + * For example, this converts Object into java/lang/Object. + */ + protected abstract String resolveClassName(String jvmClassName) + throws CompileError; + + /** + * @param name the JVM-internal representation. + * name is not exapnded to java.lang.*. + */ + protected static String toJvmArrayName(String name, int dim) { + if (name == null) + return null; + + if (dim == 0) + return name; + else { + StringBuffer sbuf = new StringBuffer(); + int d = dim; + while (d-- > 0) + sbuf.append('['); + + sbuf.append('L'); + sbuf.append(name); + sbuf.append(';'); + + return sbuf.toString(); + } + } + + protected static String toJvmTypeName(int type, int dim) { + char c = 'I'; + switch(type) { + case BOOLEAN : + c = 'Z'; + break; + case BYTE : + c = 'B'; + break; + case CHAR : + c = 'C'; + break; + case SHORT : + c = 'S'; + break; + case INT : + c = 'I'; + break; + case LONG : + c = 'J'; + break; + case FLOAT : + c = 'F'; + break; + case DOUBLE : + c = 'D'; + break; + case VOID : + c = 'V'; + break; + } + + StringBuffer sbuf = new StringBuffer(); + while (dim-- > 0) + sbuf.append('['); + + sbuf.append(c); + return sbuf.toString(); + } + + public void compileExpr(ASTree expr) throws CompileError { + doTypeCheck(expr); + expr.accept(this); + } + + public boolean compileBooleanExpr(boolean branchIf, ASTree expr) + throws CompileError + { + doTypeCheck(expr); + return booleanExpr(branchIf, expr); + } + + public void doTypeCheck(ASTree expr) throws CompileError { + if (typeChecker != null) + expr.accept(typeChecker); + } + + public void atASTList(ASTList n) throws CompileError { fatal(); } + + public void atPair(Pair n) throws CompileError { fatal(); } + + public void atSymbol(Symbol n) throws CompileError { fatal(); } + + public void atFieldDecl(FieldDecl field) throws CompileError { + field.getInit().accept(this); + } + + public void atMethodDecl(MethodDecl method) throws CompileError { + ASTList mods = method.getModifiers(); + setMaxLocals(1); + while (mods != null) { + Keyword k = (Keyword)mods.head(); + mods = mods.tail(); + if (k.get() == STATIC) { + setMaxLocals(0); + inStaticMethod = true; + } + } + + ASTList params = method.getParams(); + while (params != null) { + atDeclarator((Declarator)params.head()); + params = params.tail(); + } + + Stmnt s = method.getBody(); + atMethodBody(s, method.isConstructor(), + method.getReturn().getType() == VOID); + } + + /** + * @param isCons true if super() must be called. + * false if the method is a class initializer. + */ + public void atMethodBody(Stmnt s, boolean isCons, boolean isVoid) + throws CompileError + { + if (s == null) + return; + + if (isCons && needsSuperCall(s)) + insertDefaultSuperCall(); + + hasReturned = false; + s.accept(this); + if (!hasReturned) + if (isVoid) { + bytecode.addOpcode(Opcode.RETURN); + hasReturned = true; + } + else + throw new CompileError("no return statement"); + } + + private boolean needsSuperCall(Stmnt body) throws CompileError { + if (body.getOperator() == BLOCK) + body = (Stmnt)body.head(); + + if (body != null && body.getOperator() == EXPR) { + ASTree expr = body.head(); + if (expr != null && expr instanceof Expr + && ((Expr)expr).getOperator() == CALL) { + ASTree target = ((Expr)expr).head(); + if (target instanceof Keyword) { + int token = ((Keyword)target).get(); + return token != THIS && token != SUPER; + } + } + } + + return true; + } + + protected abstract void insertDefaultSuperCall() throws CompileError; + + public void atStmnt(Stmnt st) throws CompileError { + if (st == null) + return; // empty + + int op = st.getOperator(); + if (op == EXPR) { + ASTree expr = st.getLeft(); + doTypeCheck(expr); + if (expr instanceof AssignExpr) + atAssignExpr((AssignExpr)expr, false); + else if (isPlusPlusExpr(expr)) { + Expr e = (Expr)expr; + atPlusPlus(e.getOperator(), e.oprand1(), e, false); + } + else { + expr.accept(this); + if (is2word(exprType, arrayDim)) + bytecode.addOpcode(POP2); + else if (exprType != VOID) + bytecode.addOpcode(POP); + } + } + else if (op == DECL || op == BLOCK) { + ASTList list = st; + while (list != null) { + ASTree h = list.head(); + list = list.tail(); + if (h != null) + h.accept(this); + } + } + else if (op == IF) + atIfStmnt(st); + else if (op == WHILE || op == DO) + atWhileStmnt(st, op == WHILE); + else if (op == FOR) + atForStmnt(st); + else if (op == BREAK || op == CONTINUE) + atBreakStmnt(st, op == BREAK); + else if (op == TokenId.RETURN) + atReturnStmnt(st); + else if (op == THROW) + atThrowStmnt(st); + else if (op == TRY) + atTryStmnt(st); + else if (op == SWITCH) + atSwitchStmnt(st); + else if (op == SYNCHRONIZED) + atSyncStmnt(st); + else { + // LABEL, SWITCH label stament might be null?. + hasReturned = false; + throw new CompileError( + "sorry, not supported statement: TokenId " + op); + } + } + + private void atIfStmnt(Stmnt st) throws CompileError { + ASTree expr = st.head(); + Stmnt thenp = (Stmnt)st.tail().head(); + Stmnt elsep = (Stmnt)st.tail().tail().head(); + compileBooleanExpr(false, expr); + int pc = bytecode.currentPc(); + int pc2 = 0; + bytecode.addIndex(0); // correct later + + hasReturned = false; + if (thenp != null) + thenp.accept(this); + + boolean thenHasReturned = hasReturned; + hasReturned = false; + + if (elsep != null && !thenHasReturned) { + bytecode.addOpcode(Opcode.GOTO); + pc2 = bytecode.currentPc(); + bytecode.addIndex(0); + } + + bytecode.write16bit(pc, bytecode.currentPc() - pc + 1); + + if (elsep != null) { + elsep.accept(this); + if (!thenHasReturned) + bytecode.write16bit(pc2, bytecode.currentPc() - pc2 + 1); + + hasReturned = thenHasReturned && hasReturned; + } + } + + private void atWhileStmnt(Stmnt st, boolean notDo) throws CompileError { + ArrayList prevBreakList = breakList; + ArrayList prevContList = continueList; + breakList = new ArrayList(); + continueList = new ArrayList(); + + ASTree expr = st.head(); + Stmnt body = (Stmnt)st.tail(); + + int pc = 0; + if (notDo) { + bytecode.addOpcode(Opcode.GOTO); + pc = bytecode.currentPc(); + bytecode.addIndex(0); + } + + int pc2 = bytecode.currentPc(); + if (body != null) + body.accept(this); + + int pc3 = bytecode.currentPc(); + if (notDo) + bytecode.write16bit(pc, pc3 - pc + 1); + + boolean alwaysBranch = compileBooleanExpr(true, expr); + bytecode.addIndex(pc2 - bytecode.currentPc() + 1); + + patchGoto(breakList, bytecode.currentPc()); + patchGoto(continueList, pc3); + continueList = prevContList; + breakList = prevBreakList; + hasReturned = alwaysBranch; + } + + protected void patchGoto(ArrayList list, int targetPc) { + int n = list.size(); + for (int i = 0; i < n; ++i) { + int pc = ((Integer)list.get(i)).intValue(); + bytecode.write16bit(pc, targetPc - pc + 1); + } + } + + private void atForStmnt(Stmnt st) throws CompileError { + ArrayList prevBreakList = breakList; + ArrayList prevContList = continueList; + breakList = new ArrayList(); + continueList = new ArrayList(); + + Stmnt init = (Stmnt)st.head(); + ASTList p = st.tail(); + ASTree expr = p.head(); + p = p.tail(); + Stmnt update = (Stmnt)p.head(); + Stmnt body = (Stmnt)p.tail(); + + if (init != null) + init.accept(this); + + int pc = bytecode.currentPc(); + int pc2 = 0; + if (expr != null) { + compileBooleanExpr(false, expr); + pc2 = bytecode.currentPc(); + bytecode.addIndex(0); + } + + if (body != null) + body.accept(this); + + int pc3 = bytecode.currentPc(); + if (update != null) + update.accept(this); + + bytecode.addOpcode(Opcode.GOTO); + bytecode.addIndex(pc - bytecode.currentPc() + 1); + + int pc4 = bytecode.currentPc(); + if (expr != null) + bytecode.write16bit(pc2, pc4 - pc2 + 1); + + patchGoto(breakList, pc4); + patchGoto(continueList, pc3); + continueList = prevContList; + breakList = prevBreakList; + hasReturned = false; + } + + private void atSwitchStmnt(Stmnt st) throws CompileError { + compileExpr(st.head()); + + ArrayList prevBreakList = breakList; + breakList = new ArrayList(); + int opcodePc = bytecode.currentPc(); + bytecode.addOpcode(LOOKUPSWITCH); + int npads = 3 - (opcodePc & 3); + while (npads-- > 0) + bytecode.add(0); + + Stmnt body = (Stmnt)st.tail(); + int npairs = 0; + for (ASTList list = body; list != null; list = list.tail()) + if (((Stmnt)list.head()).getOperator() == CASE) + ++npairs; + + // opcodePc2 is the position at which the default jump offset is. + int opcodePc2 = bytecode.currentPc(); + bytecode.addGap(4); + bytecode.add32bit(npairs); + bytecode.addGap(npairs * 8); + + long[] pairs = new long[npairs]; + int ipairs = 0; + int defaultPc = -1; + for (ASTList list = body; list != null; list = list.tail()) { + Stmnt label = (Stmnt)list.head(); + int op = label.getOperator(); + if (op == DEFAULT) + defaultPc = bytecode.currentPc(); + else if (op != CASE) + fatal(); + else { + pairs[ipairs++] + = ((long)computeLabel(label.head()) << 32) + + ((long)(bytecode.currentPc() - opcodePc) & 0xffffffff); + } + + hasReturned = false; + ((Stmnt)label.tail()).accept(this); + } + + Arrays.sort(pairs); + int pc = opcodePc2 + 8; + for (int i = 0; i < npairs; ++i) { + bytecode.write32bit(pc, (int)(pairs[i] >>> 32)); + bytecode.write32bit(pc + 4, (int)pairs[i]); + pc += 8; + } + + if (defaultPc < 0 || breakList.size() > 0) + hasReturned = false; + + int endPc = bytecode.currentPc(); + if (defaultPc < 0) + defaultPc = endPc; + + bytecode.write32bit(opcodePc2, defaultPc - opcodePc); + + patchGoto(breakList, endPc); + breakList = prevBreakList; + } + + private int computeLabel(ASTree expr) throws CompileError { + doTypeCheck(expr); + expr = TypeChecker.stripPlusExpr(expr); + if (expr instanceof IntConst) + return (int)((IntConst)expr).get(); + else + throw new CompileError("bad case label"); + } + + private void atBreakStmnt(Stmnt st, boolean notCont) + throws CompileError + { + if (st.head() != null) + throw new CompileError( + "sorry, not support labeled break or continue"); + + bytecode.addOpcode(Opcode.GOTO); + Integer pc = new Integer(bytecode.currentPc()); + bytecode.addIndex(0); + if (notCont) + breakList.add(pc); + else + continueList.add(pc); + } + + protected void atReturnStmnt(Stmnt st) throws CompileError { + atReturnStmnt2(st.getLeft()); + } + + protected final void atReturnStmnt2(ASTree result) throws CompileError { + int op; + if (result == null) + op = Opcode.RETURN; + else { + compileExpr(result); + if (arrayDim > 0) + op = ARETURN; + else { + int type = exprType; + if (type == DOUBLE) + op = DRETURN; + else if (type == FLOAT) + op = FRETURN; + else if (type == LONG) + op = LRETURN; + else if (isRefType(type)) + op = ARETURN; + else + op = IRETURN; + } + } + + for (ReturnHook har = returnHooks; har != null; har = har.next) + if (har.doit(bytecode, op)) { + hasReturned = true; + return; + } + + bytecode.addOpcode(op); + hasReturned = true; + } + + private void atThrowStmnt(Stmnt st) throws CompileError { + ASTree e = st.getLeft(); + compileExpr(e); + if (exprType != CLASS || arrayDim > 0) + throw new CompileError("bad throw statement"); + + bytecode.addOpcode(ATHROW); + hasReturned = true; + } + + /* overridden in MemberCodeGen + */ + protected void atTryStmnt(Stmnt st) throws CompileError { + hasReturned = false; + } + + private void atSyncStmnt(Stmnt st) throws CompileError { + int nbreaks = getListSize(breakList); + int ncontinues = getListSize(continueList); + + compileExpr(st.head()); + if (exprType != CLASS && arrayDim == 0) + throw new CompileError("bad type expr for synchronized block"); + + Bytecode bc = bytecode; + final int var = bc.getMaxLocals(); + bc.incMaxLocals(1); + bc.addOpcode(DUP); + bc.addAstore(var); + bc.addOpcode(MONITORENTER); + + ReturnHook rh = new ReturnHook(this) { + protected boolean doit(Bytecode b, int opcode) { + b.addAload(var); + b.addOpcode(MONITOREXIT); + return false; + } + }; + + int pc = bc.currentPc(); + Stmnt body = (Stmnt)st.tail(); + if (body != null) + body.accept(this); + + int pc2 = bc.currentPc(); + int pc3 = 0; + if (!hasReturned) { + rh.doit(bc, 0); // the 2nd arg is ignored. + bc.addOpcode(Opcode.GOTO); + pc3 = bc.currentPc(); + bc.addIndex(0); + } + + if (pc < pc2) { // if the body is not empty + int pc4 = bc.currentPc(); + rh.doit(bc, 0); // the 2nd arg is ignored. + bc.addOpcode(ATHROW); + bc.addExceptionHandler(pc, pc2, pc4, 0); + } + + if (!hasReturned) + bc.write16bit(pc3, bc.currentPc() - pc3 + 1); + + rh.remove(this); + + if (getListSize(breakList) != nbreaks + || getListSize(continueList) != ncontinues) + throw new CompileError( + "sorry, cannot break/continue in synchronized block"); + } + + private static int getListSize(ArrayList list) { + return list == null ? 0 : list.size(); + } + + private static boolean isPlusPlusExpr(ASTree expr) { + if (expr instanceof Expr) { + int op = ((Expr)expr).getOperator(); + return op == PLUSPLUS || op == MINUSMINUS; + } + + return false; + } + + public void atDeclarator(Declarator d) throws CompileError { + d.setLocalVar(getMaxLocals()); + d.setClassName(resolveClassName(d.getClassName())); + + int size; + if (is2word(d.getType(), d.getArrayDim())) + size = 2; + else + size = 1; + + incMaxLocals(size); + + /* NOTE: Array initializers has not been supported. + */ + ASTree init = d.getInitializer(); + if (init != null) { + doTypeCheck(init); + atVariableAssign(null, '=', null, d, init, false); + } + } + + public abstract void atNewExpr(NewExpr n) throws CompileError; + + public abstract void atArrayInit(ArrayInit init) throws CompileError; + + public void atAssignExpr(AssignExpr expr) throws CompileError { + atAssignExpr(expr, true); + } + + protected void atAssignExpr(AssignExpr expr, boolean doDup) + throws CompileError + { + // =, %=, &=, *=, /=, +=, -=, ^=, |=, <<=, >>=, >>>= + int op = expr.getOperator(); + ASTree left = expr.oprand1(); + ASTree right = expr.oprand2(); + if (left instanceof Variable) + atVariableAssign(expr, op, (Variable)left, + ((Variable)left).getDeclarator(), + right, doDup); + else { + if (left instanceof Expr) { + Expr e = (Expr)left; + if (e.getOperator() == ARRAY) { + atArrayAssign(expr, op, (Expr)left, right, doDup); + return; + } + } + + atFieldAssign(expr, op, left, right, doDup); + } + } + + protected static void badAssign(Expr expr) throws CompileError { + String msg; + if (expr == null) + msg = "incompatible type for assignment"; + else + msg = "incompatible type for " + expr.getName(); + + throw new CompileError(msg); + } + + /* op is either =, %=, &=, *=, /=, +=, -=, ^=, |=, <<=, >>=, or >>>=. + * + * expr and var can be null. + */ + private void atVariableAssign(Expr expr, int op, Variable var, + Declarator d, ASTree right, + boolean doDup) throws CompileError + { + int varType = d.getType(); + int varArray = d.getArrayDim(); + String varClass = d.getClassName(); + int varNo = getLocalVar(d); + + if (op != '=') + atVariable(var); + + // expr is null if the caller is atDeclarator(). + if (expr == null && right instanceof ArrayInit) + atArrayVariableAssign((ArrayInit)right, varType, varArray, varClass); + else + atAssignCore(expr, op, right, varType, varArray, varClass); + + if (doDup) + if (is2word(varType, varArray)) + bytecode.addOpcode(DUP2); + else + bytecode.addOpcode(DUP); + + if (varArray > 0) + bytecode.addAstore(varNo); + else if (varType == DOUBLE) + bytecode.addDstore(varNo); + else if (varType == FLOAT) + bytecode.addFstore(varNo); + else if (varType == LONG) + bytecode.addLstore(varNo); + else if (isRefType(varType)) + bytecode.addAstore(varNo); + else + bytecode.addIstore(varNo); + + exprType = varType; + arrayDim = varArray; + className = varClass; + } + + protected abstract void atArrayVariableAssign(ArrayInit init, + int varType, int varArray, String varClass) throws CompileError; + + private void atArrayAssign(Expr expr, int op, Expr array, + ASTree right, boolean doDup) throws CompileError + { + arrayAccess(array.oprand1(), array.oprand2()); + + if (op != '=') { + bytecode.addOpcode(DUP2); + bytecode.addOpcode(getArrayReadOp(exprType, arrayDim)); + } + + int aType = exprType; + int aDim = arrayDim; + String cname = className; + + atAssignCore(expr, op, right, aType, aDim, cname); + + if (doDup) + if (is2word(aType, aDim)) + bytecode.addOpcode(DUP2_X2); + else + bytecode.addOpcode(DUP_X2); + + bytecode.addOpcode(getArrayWriteOp(aType, aDim)); + exprType = aType; + arrayDim = aDim; + className = cname; + } + + protected abstract void atFieldAssign(Expr expr, int op, ASTree left, + ASTree right, boolean doDup) throws CompileError; + + protected void atAssignCore(Expr expr, int op, ASTree right, + int type, int dim, String cname) + throws CompileError + { + if (op == PLUS_E && dim == 0 && type == CLASS) + atStringPlusEq(expr, type, dim, cname, right); + else { + right.accept(this); + if (invalidDim(exprType, arrayDim, className, type, dim, cname, + false) || (op != '=' && dim > 0)) + badAssign(expr); + + if (op != '=') { + int token = assignOps[op - MOD_E]; + int k = lookupBinOp(token); + if (k < 0) + fatal(); + + atArithBinExpr(expr, token, k, type); + } + } + + if (op != '=' || (dim == 0 && !isRefType(type))) + atNumCastExpr(exprType, type); + + // type check should be done here. + } + + private void atStringPlusEq(Expr expr, int type, int dim, String cname, + ASTree right) + throws CompileError + { + if (!jvmJavaLangString.equals(cname)) + badAssign(expr); + + convToString(type, dim); // the value might be null. + right.accept(this); + convToString(exprType, arrayDim); + bytecode.addInvokevirtual(javaLangString, "concat", + "(Ljava/lang/String;)Ljava/lang/String;"); + exprType = CLASS; + arrayDim = 0; + className = jvmJavaLangString; + } + + private boolean invalidDim(int srcType, int srcDim, String srcClass, + int destType, int destDim, String destClass, + boolean isCast) + { + if (srcDim != destDim) + if (srcType == NULL) + return false; + else if (destDim == 0 && destType == CLASS + && jvmJavaLangObject.equals(destClass)) + return false; + else if (isCast && srcDim == 0 && srcType == CLASS + && jvmJavaLangObject.equals(srcClass)) + return false; + else + return true; + + return false; + } + + public void atCondExpr(CondExpr expr) throws CompileError { + booleanExpr(false, expr.condExpr()); + int pc = bytecode.currentPc(); + bytecode.addIndex(0); // correct later + expr.thenExpr().accept(this); + int dim1 = arrayDim; + bytecode.addOpcode(Opcode.GOTO); + int pc2 = bytecode.currentPc(); + bytecode.addIndex(0); + bytecode.write16bit(pc, bytecode.currentPc() - pc + 1); + expr.elseExpr().accept(this); + if (dim1 != arrayDim) + throw new CompileError("type mismatch in ?:"); + + bytecode.write16bit(pc2, bytecode.currentPc() - pc2 + 1); + } + + static final int[] binOp = { + '+', DADD, FADD, LADD, IADD, + '-', DSUB, FSUB, LSUB, ISUB, + '*', DMUL, FMUL, LMUL, IMUL, + '/', DDIV, FDIV, LDIV, IDIV, + '%', DREM, FREM, LREM, IREM, + '|', NOP, NOP, LOR, IOR, + '^', NOP, NOP, LXOR, IXOR, + '&', NOP, NOP, LAND, IAND, + LSHIFT, NOP, NOP, LSHL, ISHL, + RSHIFT, NOP, NOP, LSHR, ISHR, + ARSHIFT, NOP, NOP, LUSHR, IUSHR }; + + static int lookupBinOp(int token) { + int[] code = binOp; + int s = code.length; + for (int k = 0; k < s; k = k + 5) + if (code[k] == token) + return k; + + return -1; + } + + public void atBinExpr(BinExpr expr) throws CompileError { + int token = expr.getOperator(); + + /* arithmetic operators: +, -, *, /, %, |, ^, &, <<, >>, >>> + */ + int k = lookupBinOp(token); + if (k >= 0) { + expr.oprand1().accept(this); + ASTree right = expr.oprand2(); + if (right == null) + return; // see TypeChecker.atBinExpr(). + + int type1 = exprType; + int dim1 = arrayDim; + String cname1 = className; + right.accept(this); + if (dim1 != arrayDim) + throw new CompileError("incompatible array types"); + + if (token == '+' && dim1 == 0 + && (type1 == CLASS || exprType == CLASS)) + atStringConcatExpr(expr, type1, dim1, cname1); + else + atArithBinExpr(expr, token, k, type1); + } + else { + /* equation: &&, ||, ==, !=, <=, >=, <, > + */ + booleanExpr(true, expr); + bytecode.addIndex(7); + bytecode.addIconst(0); // false + bytecode.addOpcode(Opcode.GOTO); + bytecode.addIndex(4); + bytecode.addIconst(1); // true + } + } + + /* arrayDim values of the two oprands must be equal. + * If an oprand type is not a numeric type, this method + * throws an exception. + */ + private void atArithBinExpr(Expr expr, int token, + int index, int type1) throws CompileError + { + if (arrayDim != 0) + badTypes(expr); + + int type2 = exprType; + if (token == LSHIFT || token == RSHIFT || token == ARSHIFT) + if (type2 == INT || type2 == SHORT + || type2 == CHAR || type2 == BYTE) + exprType = type1; + else + badTypes(expr); + else + convertOprandTypes(type1, type2, expr); + + int p = typePrecedence(exprType); + if (p >= 0) { + int op = binOp[index + p + 1]; + if (op != NOP) { + if (p == P_INT && exprType != BOOLEAN) + exprType = INT; // type1 may be BYTE, ... + + bytecode.addOpcode(op); + return; + } + } + + badTypes(expr); + } + + private void atStringConcatExpr(Expr expr, int type1, int dim1, + String cname1) throws CompileError + { + int type2 = exprType; + int dim2 = arrayDim; + boolean type2Is2 = is2word(type2, dim2); + boolean type2IsString + = (type2 == CLASS && jvmJavaLangString.equals(className)); + + if (type2Is2) + convToString(type2, dim2); + + if (is2word(type1, dim1)) { + bytecode.addOpcode(DUP_X2); + bytecode.addOpcode(POP); + } + else + bytecode.addOpcode(SWAP); + + // even if type1 is String, the left operand might be null. + convToString(type1, dim1); + bytecode.addOpcode(SWAP); + + if (!type2Is2 && !type2IsString) + convToString(type2, dim2); + + bytecode.addInvokevirtual(javaLangString, "concat", + "(Ljava/lang/String;)Ljava/lang/String;"); + exprType = CLASS; + arrayDim = 0; + className = jvmJavaLangString; + } + + private void convToString(int type, int dim) throws CompileError { + final String method = "valueOf"; + + if (isRefType(type) || dim > 0) + bytecode.addInvokestatic(javaLangString, method, + "(Ljava/lang/Object;)Ljava/lang/String;"); + else if (type == DOUBLE) + bytecode.addInvokestatic(javaLangString, method, + "(D)Ljava/lang/String;"); + else if (type == FLOAT) + bytecode.addInvokestatic(javaLangString, method, + "(F)Ljava/lang/String;"); + else if (type == LONG) + bytecode.addInvokestatic(javaLangString, method, + "(J)Ljava/lang/String;"); + else if (type == BOOLEAN) + bytecode.addInvokestatic(javaLangString, method, + "(Z)Ljava/lang/String;"); + else if (type == CHAR) + bytecode.addInvokestatic(javaLangString, method, + "(C)Ljava/lang/String;"); + else if (type == VOID) + throw new CompileError("void type expression"); + else /* INT, BYTE, SHORT */ + bytecode.addInvokestatic(javaLangString, method, + "(I)Ljava/lang/String;"); + } + + /* Produces the opcode to branch if the condition is true. + * The oprand is not produced. + * + * @return true if the compiled code is GOTO (always branch). + */ + private boolean booleanExpr(boolean branchIf, ASTree expr) + throws CompileError + { + boolean isAndAnd; + int op = getCompOperator(expr); + if (op == EQ) { // ==, !=, ... + BinExpr bexpr = (BinExpr)expr; + int type1 = compileOprands(bexpr); + // here, arrayDim might represent the array dim. of the left oprand + // if the right oprand is NULL. + compareExpr(branchIf, bexpr.getOperator(), type1, bexpr); + } + else if (op == '!') + booleanExpr(!branchIf, ((Expr)expr).oprand1()); + else if ((isAndAnd = (op == ANDAND)) || op == OROR) { + BinExpr bexpr = (BinExpr)expr; + booleanExpr(!isAndAnd, bexpr.oprand1()); + int pc = bytecode.currentPc(); + bytecode.addIndex(0); // correct later + + booleanExpr(isAndAnd, bexpr.oprand2()); + bytecode.write16bit(pc, bytecode.currentPc() - pc + 3); + if (branchIf != isAndAnd) { + bytecode.addIndex(6); // skip GOTO instruction + bytecode.addOpcode(Opcode.GOTO); + } + } + else if (isAlwaysBranch(expr, branchIf)) { + bytecode.addOpcode(Opcode.GOTO); + return true; // always branch + } + else { // others + expr.accept(this); + if (exprType != BOOLEAN || arrayDim != 0) + throw new CompileError("boolean expr is required"); + + bytecode.addOpcode(branchIf ? IFNE : IFEQ); + } + + exprType = BOOLEAN; + arrayDim = 0; + return false; + } + + + private static boolean isAlwaysBranch(ASTree expr, boolean branchIf) { + if (expr instanceof Keyword) { + int t = ((Keyword)expr).get(); + return branchIf ? t == TRUE : t == FALSE; + } + + return false; + } + + static int getCompOperator(ASTree expr) throws CompileError { + if (expr instanceof Expr) { + Expr bexpr = (Expr)expr; + int token = bexpr.getOperator(); + if (token == '!') + return '!'; + else if ((bexpr instanceof BinExpr) + && token != OROR && token != ANDAND + && token != '&' && token != '|') + return EQ; // ==, !=, ... + else + return token; + } + + return ' '; // others + } + + private int compileOprands(BinExpr expr) throws CompileError { + expr.oprand1().accept(this); + int type1 = exprType; + int dim1 = arrayDim; + expr.oprand2().accept(this); + if (dim1 != arrayDim) + if (type1 != NULL && exprType != NULL) + throw new CompileError("incompatible array types"); + else if (exprType == NULL) + arrayDim = dim1; + + if (type1 == NULL) + return exprType; + else + return type1; + } + + private static final int ifOp[] = { EQ, IF_ICMPEQ, IF_ICMPNE, + NEQ, IF_ICMPNE, IF_ICMPEQ, + LE, IF_ICMPLE, IF_ICMPGT, + GE, IF_ICMPGE, IF_ICMPLT, + '<', IF_ICMPLT, IF_ICMPGE, + '>', IF_ICMPGT, IF_ICMPLE }; + + private static final int ifOp2[] = { EQ, IFEQ, IFNE, + NEQ, IFNE, IFEQ, + LE, IFLE, IFGT, + GE, IFGE, IFLT, + '<', IFLT, IFGE, + '>', IFGT, IFLE }; + + /* Produces the opcode to branch if the condition is true. + * The oprands are not produced. + * + * Parameter expr - compare expression ==, !=, <=, >=, <, > + */ + private void compareExpr(boolean branchIf, + int token, int type1, BinExpr expr) + throws CompileError + { + if (arrayDim == 0) + convertOprandTypes(type1, exprType, expr); + + int p = typePrecedence(exprType); + if (p == P_OTHER || arrayDim > 0) + if (token == EQ) + bytecode.addOpcode(branchIf ? IF_ACMPEQ : IF_ACMPNE); + else if (token == NEQ) + bytecode.addOpcode(branchIf ? IF_ACMPNE : IF_ACMPEQ); + else + badTypes(expr); + else + if (p == P_INT) { + int op[] = ifOp; + for (int i = 0; i < op.length; i += 3) + if (op[i] == token) { + bytecode.addOpcode(op[i + (branchIf ? 1 : 2)]); + return; + } + + badTypes(expr); + } + else { + if (p == P_DOUBLE) + if (token == '<' || token == LE) + bytecode.addOpcode(DCMPG); + else + bytecode.addOpcode(DCMPL); + else if (p == P_FLOAT) + if (token == '<' || token == LE) + bytecode.addOpcode(FCMPG); + else + bytecode.addOpcode(FCMPL); + else if (p == P_LONG) + bytecode.addOpcode(LCMP); // 1: >, 0: =, -1: < + else + fatal(); + + int[] op = ifOp2; + for (int i = 0; i < op.length; i += 3) + if (op[i] == token) { + bytecode.addOpcode(op[i + (branchIf ? 1 : 2)]); + return; + } + + badTypes(expr); + } + } + + protected static void badTypes(Expr expr) throws CompileError { + throw new CompileError("invalid types for " + expr.getName()); + } + + private static final int P_DOUBLE = 0; + private static final int P_FLOAT = 1; + private static final int P_LONG = 2; + private static final int P_INT = 3; + private static final int P_OTHER = -1; + + protected static boolean isRefType(int type) { + return type == CLASS || type == NULL; + } + + private static int typePrecedence(int type) { + if (type == DOUBLE) + return P_DOUBLE; + else if (type == FLOAT) + return P_FLOAT; + else if (type == LONG) + return P_LONG; + else if (isRefType(type)) + return P_OTHER; + else if (type == VOID) + return P_OTHER; // this is wrong, but ... + else + return P_INT; // BOOLEAN, BYTE, CHAR, SHORT, INT + } + + // used in TypeChecker. + static boolean isP_INT(int type) { + return typePrecedence(type) == P_INT; + } + + // used in TypeChecker. + static boolean rightIsStrong(int type1, int type2) { + int type1_p = typePrecedence(type1); + int type2_p = typePrecedence(type2); + return type1_p >= 0 && type2_p >= 0 && type1_p > type2_p; + } + + private static final int[] castOp = { + /* D F L I */ + /* double */ NOP, D2F, D2L, D2I, + /* float */ F2D, NOP, F2L, F2I, + /* long */ L2D, L2F, NOP, L2I, + /* other */ I2D, I2F, I2L, NOP }; + + /* do implicit type conversion. + * arrayDim values of the two oprands must be zero. + */ + private void convertOprandTypes(int type1, int type2, Expr expr) + throws CompileError + { + boolean rightStrong; + int type1_p = typePrecedence(type1); + int type2_p = typePrecedence(type2); + + if (type2_p < 0 && type1_p < 0) // not primitive types + return; + + if (type2_p < 0 || type1_p < 0) // either is not a primitive type + badTypes(expr); + + int op, result_type; + if (type1_p <= type2_p) { + rightStrong = false; + exprType = type1; + op = castOp[type2_p * 4 + type1_p]; + result_type = type1_p; + } + else { + rightStrong = true; + op = castOp[type1_p * 4 + type2_p]; + result_type = type2_p; + } + + if (rightStrong) { + if (result_type == P_DOUBLE || result_type == P_LONG) { + if (type1_p == P_DOUBLE || type1_p == P_LONG) + bytecode.addOpcode(DUP2_X2); + else + bytecode.addOpcode(DUP2_X1); + + bytecode.addOpcode(POP2); + bytecode.addOpcode(op); + bytecode.addOpcode(DUP2_X2); + bytecode.addOpcode(POP2); + } + else if (result_type == P_FLOAT) { + if (type1_p == P_LONG) { + bytecode.addOpcode(DUP_X2); + bytecode.addOpcode(POP); + } + else + bytecode.addOpcode(SWAP); + + bytecode.addOpcode(op); + bytecode.addOpcode(SWAP); + } + else + fatal(); + } + else if (op != NOP) + bytecode.addOpcode(op); + } + + public void atCastExpr(CastExpr expr) throws CompileError { + String cname = resolveClassName(expr.getClassName()); + String toClass = checkCastExpr(expr, cname); + int srcType = exprType; + exprType = expr.getType(); + arrayDim = expr.getArrayDim(); + className = cname; + if (toClass == null) + atNumCastExpr(srcType, exprType); // built-in type + else + bytecode.addCheckcast(toClass); + } + + public void atInstanceOfExpr(InstanceOfExpr expr) throws CompileError { + String cname = resolveClassName(expr.getClassName()); + String toClass = checkCastExpr(expr, cname); + bytecode.addInstanceof(toClass); + exprType = BOOLEAN; + arrayDim = 0; + } + + private String checkCastExpr(CastExpr expr, String name) + throws CompileError + { + final String msg = "invalid cast"; + ASTree oprand = expr.getOprand(); + int dim = expr.getArrayDim(); + int type = expr.getType(); + oprand.accept(this); + int srcType = exprType; + if (invalidDim(srcType, arrayDim, className, type, dim, name, true) + || srcType == VOID || type == VOID) + throw new CompileError(msg); + + if (type == CLASS) { + if (!isRefType(srcType)) + throw new CompileError(msg); + + return toJvmArrayName(name, dim); + } + else + if (dim > 0) + return toJvmTypeName(type, dim); + else + return null; // built-in type + } + + void atNumCastExpr(int srcType, int destType) + throws CompileError + { + if (srcType == destType) + return; + + int op, op2; + int stype = typePrecedence(srcType); + int dtype = typePrecedence(destType); + if (0 <= stype && stype < 3) + op = castOp[stype * 4 + dtype]; + else + op = NOP; + + if (destType == DOUBLE) + op2 = I2D; + else if (destType == FLOAT) + op2 = I2F; + else if (destType == LONG) + op2 = I2L; + else if (destType == SHORT) + op2 = I2S; + else if (destType == CHAR) + op2 = I2C; + else if (destType == BYTE) + op2 = I2B; + else + op2 = NOP; + + if (op != NOP) + bytecode.addOpcode(op); + + if (op == NOP || op == L2I || op == F2I || op == D2I) + if (op2 != NOP) + bytecode.addOpcode(op2); + } + + public void atExpr(Expr expr) throws CompileError { + // array access, member access, + // (unary) +, (unary) -, ++, --, !, ~ + + int token = expr.getOperator(); + ASTree oprand = expr.oprand1(); + if (token == '.') { + String member = ((Symbol)expr.oprand2()).get(); + if (member.equals("class")) + atClassObject(expr); // .class + else + atFieldRead(expr); + } + else if (token == MEMBER) { // field read + /* MEMBER ('#') is an extension by Javassist. + * The compiler internally uses # for compiling .class + * expressions such as "int.class". + */ + atFieldRead(expr); + } + else if (token == ARRAY) + atArrayRead(oprand, expr.oprand2()); + else if (token == PLUSPLUS || token == MINUSMINUS) + atPlusPlus(token, oprand, expr, true); + else if (token == '!') { + booleanExpr(false, expr); + bytecode.addIndex(7); + bytecode.addIconst(1); + bytecode.addOpcode(Opcode.GOTO); + bytecode.addIndex(4); + bytecode.addIconst(0); + } + else if (token == CALL) // method call + fatal(); + else { + expr.oprand1().accept(this); + int type = typePrecedence(exprType); + if (arrayDim > 0) + badType(expr); + + if (token == '-') { + if (type == P_DOUBLE) + bytecode.addOpcode(DNEG); + else if (type == P_FLOAT) + bytecode.addOpcode(FNEG); + else if (type == P_LONG) + bytecode.addOpcode(LNEG); + else if (type == P_INT) { + bytecode.addOpcode(INEG); + exprType = INT; // type may be BYTE, ... + } + else + badType(expr); + } + else if (token == '~') { + if (type == P_INT) { + bytecode.addIconst(-1); + bytecode.addOpcode(IXOR); + exprType = INT; // type may be BYTE. ... + } + else if (type == P_LONG) { + bytecode.addLconst(-1); + bytecode.addOpcode(LXOR); + } + else + badType(expr); + + } + else if (token == '+') { + if (type == P_OTHER) + badType(expr); + + // do nothing. ignore. + } + else + fatal(); + } + } + + protected static void badType(Expr expr) throws CompileError { + throw new CompileError("invalid type for " + expr.getName()); + } + + public abstract void atCallExpr(CallExpr expr) throws CompileError; + + protected abstract void atFieldRead(ASTree expr) throws CompileError; + + public void atClassObject(Expr expr) throws CompileError { + ASTree op1 = expr.oprand1(); + if (!(op1 instanceof Symbol)) + throw new CompileError("fatal error: badly parsed .class expr"); + + String cname = ((Symbol)op1).get(); + if (cname.startsWith("[")) { + int i = cname.indexOf("[L"); + if (i >= 0) { + String name = cname.substring(i + 2, cname.length() - 1); + String name2 = resolveClassName(name); + if (!name.equals(name2)) { + /* For example, to obtain String[].class, + * "[Ljava.lang.String;" (not "[Ljava/lang/String"!) + * must be passed to Class.forName(). + */ + name2 = MemberResolver.jvmToJavaName(name2); + StringBuffer sbuf = new StringBuffer(); + while (i-- >= 0) + sbuf.append('['); + + sbuf.append('L').append(name2).append(';'); + cname = sbuf.toString(); + } + } + } + else { + cname = resolveClassName(MemberResolver.javaToJvmName(cname)); + cname = MemberResolver.jvmToJavaName(cname); + } + + atClassObject2(cname); + exprType = CLASS; + arrayDim = 0; + className = "java/lang/Class"; + } + + /* MemberCodeGen overrides this method. + */ + protected void atClassObject2(String cname) throws CompileError { + int start = bytecode.currentPc(); + bytecode.addLdc(cname); + bytecode.addInvokestatic("java.lang.Class", "forName", + "(Ljava/lang/String;)Ljava/lang/Class;"); + int end = bytecode.currentPc(); + bytecode.addOpcode(Opcode.GOTO); + int pc = bytecode.currentPc(); + bytecode.addIndex(0); // correct later + + bytecode.addExceptionHandler(start, end, bytecode.currentPc(), + "java.lang.ClassNotFoundException"); + + /* -- the following code is for inlining a call to DotClass.fail(). + + int var = getMaxLocals(); + incMaxLocals(1); + bytecode.growStack(1); + bytecode.addAstore(var); + + bytecode.addNew("java.lang.NoClassDefFoundError"); + bytecode.addOpcode(DUP); + bytecode.addAload(var); + bytecode.addInvokevirtual("java.lang.ClassNotFoundException", + "getMessage", "()Ljava/lang/String;"); + bytecode.addInvokespecial("java.lang.NoClassDefFoundError", "<init>", + "(Ljava/lang/String;)V"); + */ + + bytecode.growStack(1); + bytecode.addInvokestatic("javassist.runtime.DotClass", "fail", + "(Ljava/lang/ClassNotFoundException;)" + + "Ljava/lang/NoClassDefFoundError;"); + bytecode.addOpcode(ATHROW); + bytecode.write16bit(pc, bytecode.currentPc() - pc + 1); + } + + public void atArrayRead(ASTree array, ASTree index) + throws CompileError + { + arrayAccess(array, index); + bytecode.addOpcode(getArrayReadOp(exprType, arrayDim)); + } + + protected void arrayAccess(ASTree array, ASTree index) + throws CompileError + { + array.accept(this); + int type = exprType; + int dim = arrayDim; + if (dim == 0) + throw new CompileError("bad array access"); + + String cname = className; + + index.accept(this); + if (typePrecedence(exprType) != P_INT || arrayDim > 0) + throw new CompileError("bad array index"); + + exprType = type; + arrayDim = dim - 1; + className = cname; + } + + protected static int getArrayReadOp(int type, int dim) { + if (dim > 0) + return AALOAD; + + switch (type) { + case DOUBLE : + return DALOAD; + case FLOAT : + return FALOAD; + case LONG : + return LALOAD; + case INT : + return IALOAD; + case SHORT : + return SALOAD; + case CHAR : + return CALOAD; + case BYTE : + case BOOLEAN : + return BALOAD; + default : + return AALOAD; + } + } + + protected static int getArrayWriteOp(int type, int dim) { + if (dim > 0) + return AASTORE; + + switch (type) { + case DOUBLE : + return DASTORE; + case FLOAT : + return FASTORE; + case LONG : + return LASTORE; + case INT : + return IASTORE; + case SHORT : + return SASTORE; + case CHAR : + return CASTORE; + case BYTE : + case BOOLEAN : + return BASTORE; + default : + return AASTORE; + } + } + + private void atPlusPlus(int token, ASTree oprand, Expr expr, + boolean doDup) throws CompileError + { + boolean isPost = oprand == null; // ++i or i++? + if (isPost) + oprand = expr.oprand2(); + + if (oprand instanceof Variable) { + Declarator d = ((Variable)oprand).getDeclarator(); + int t = exprType = d.getType(); + arrayDim = d.getArrayDim(); + int var = getLocalVar(d); + if (arrayDim > 0) + badType(expr); + + if (t == DOUBLE) { + bytecode.addDload(var); + if (doDup && isPost) + bytecode.addOpcode(DUP2); + + bytecode.addDconst(1.0); + bytecode.addOpcode(token == PLUSPLUS ? DADD : DSUB); + if (doDup && !isPost) + bytecode.addOpcode(DUP2); + + bytecode.addDstore(var); + } + else if (t == LONG) { + bytecode.addLload(var); + if (doDup && isPost) + bytecode.addOpcode(DUP2); + + bytecode.addLconst((long)1); + bytecode.addOpcode(token == PLUSPLUS ? LADD : LSUB); + if (doDup && !isPost) + bytecode.addOpcode(DUP2); + + bytecode.addLstore(var); + } + else if (t == FLOAT) { + bytecode.addFload(var); + if (doDup && isPost) + bytecode.addOpcode(DUP); + + bytecode.addFconst(1.0f); + bytecode.addOpcode(token == PLUSPLUS ? FADD : FSUB); + if (doDup && !isPost) + bytecode.addOpcode(DUP); + + bytecode.addFstore(var); + } + else if (t == BYTE || t == CHAR || t == SHORT || t == INT) { + if (doDup && isPost) + bytecode.addIload(var); + + int delta = token == PLUSPLUS ? 1 : -1; + if (var > 0xff) { + bytecode.addOpcode(WIDE); + bytecode.addOpcode(IINC); + bytecode.addIndex(var); + bytecode.addIndex(delta); + } + else { + bytecode.addOpcode(IINC); + bytecode.add(var); + bytecode.add(delta); + } + + if (doDup && !isPost) + bytecode.addIload(var); + } + else + badType(expr); + } + else { + if (oprand instanceof Expr) { + Expr e = (Expr)oprand; + if (e.getOperator() == ARRAY) { + atArrayPlusPlus(token, isPost, e, doDup); + return; + } + } + + atFieldPlusPlus(token, isPost, oprand, expr, doDup); + } + } + + public void atArrayPlusPlus(int token, boolean isPost, + Expr expr, boolean doDup) throws CompileError + { + arrayAccess(expr.oprand1(), expr.oprand2()); + int t = exprType; + int dim = arrayDim; + if (dim > 0) + badType(expr); + + bytecode.addOpcode(DUP2); + bytecode.addOpcode(getArrayReadOp(t, arrayDim)); + int dup_code = is2word(t, dim) ? DUP2_X2 : DUP_X2; + atPlusPlusCore(dup_code, doDup, token, isPost, expr); + bytecode.addOpcode(getArrayWriteOp(t, dim)); + } + + protected void atPlusPlusCore(int dup_code, boolean doDup, + int token, boolean isPost, + Expr expr) throws CompileError + { + int t = exprType; + + if (doDup && isPost) + bytecode.addOpcode(dup_code); + + if (t == INT || t == BYTE || t == CHAR || t == SHORT) { + bytecode.addIconst(1); + bytecode.addOpcode(token == PLUSPLUS ? IADD : ISUB); + exprType = INT; + } + else if (t == LONG) { + bytecode.addLconst((long)1); + bytecode.addOpcode(token == PLUSPLUS ? LADD : LSUB); + } + else if (t == FLOAT) { + bytecode.addFconst(1.0f); + bytecode.addOpcode(token == PLUSPLUS ? FADD : FSUB); + } + else if (t == DOUBLE) { + bytecode.addDconst(1.0); + bytecode.addOpcode(token == PLUSPLUS ? DADD : DSUB); + } + else + badType(expr); + + if (doDup && !isPost) + bytecode.addOpcode(dup_code); + } + + protected abstract void atFieldPlusPlus(int token, boolean isPost, + ASTree oprand, Expr expr, boolean doDup) throws CompileError; + + public abstract void atMember(Member n) throws CompileError; + + public void atVariable(Variable v) throws CompileError { + Declarator d = v.getDeclarator(); + exprType = d.getType(); + arrayDim = d.getArrayDim(); + className = d.getClassName(); + int var = getLocalVar(d); + + if (arrayDim > 0) + bytecode.addAload(var); + else + switch (exprType) { + case CLASS : + bytecode.addAload(var); + break; + case LONG : + bytecode.addLload(var); + break; + case FLOAT : + bytecode.addFload(var); + break; + case DOUBLE : + bytecode.addDload(var); + break; + default : // BOOLEAN, BYTE, CHAR, SHORT, INT + bytecode.addIload(var); + break; + } + } + + public void atKeyword(Keyword k) throws CompileError { + arrayDim = 0; + int token = k.get(); + switch (token) { + case TRUE : + bytecode.addIconst(1); + exprType = BOOLEAN; + break; + case FALSE : + bytecode.addIconst(0); + exprType = BOOLEAN; + break; + case NULL : + bytecode.addOpcode(ACONST_NULL); + exprType = NULL; + break; + case THIS : + case SUPER : + if (inStaticMethod) + throw new CompileError("not-available: " + + (token == THIS ? "this" : "super")); + + bytecode.addAload(0); + exprType = CLASS; + if (token == THIS) + className = getThisName(); + else + className = getSuperName(); + break; + default : + fatal(); + } + } + + public void atStringL(StringL s) throws CompileError { + exprType = CLASS; + arrayDim = 0; + className = jvmJavaLangString; + bytecode.addLdc(s.get()); + } + + public void atIntConst(IntConst i) throws CompileError { + arrayDim = 0; + long value = i.get(); + int type = i.getType(); + if (type == IntConstant || type == CharConstant) { + exprType = (type == IntConstant ? INT : CHAR); + bytecode.addIconst((int)value); + } + else { + exprType = LONG; + bytecode.addLconst(value); + } + } + + public void atDoubleConst(DoubleConst d) throws CompileError { + arrayDim = 0; + if (d.getType() == DoubleConstant) { + exprType = DOUBLE; + bytecode.addDconst(d.get()); + } + else { + exprType = FLOAT; + bytecode.addFconst((float)d.get()); + } + } +} diff --git a/src/main/javassist/compiler/CompileError.java b/src/main/javassist/compiler/CompileError.java new file mode 100644 index 0000000..2756d15 --- /dev/null +++ b/src/main/javassist/compiler/CompileError.java @@ -0,0 +1,52 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.compiler; + +import javassist.CannotCompileException; +import javassist.NotFoundException; + +public class CompileError extends Exception { + private Lex lex; + private String reason; + + public CompileError(String s, Lex l) { + reason = s; + lex = l; + } + + public CompileError(String s) { + reason = s; + lex = null; + } + + public CompileError(CannotCompileException e) { + this(e.getReason()); + } + + public CompileError(NotFoundException e) { + this("cannot find " + e.getMessage()); + } + + public Lex getLex() { return lex; } + + public String getMessage() { + return reason; + } + + public String toString() { + return "compile error: " + reason; + } +} diff --git a/src/main/javassist/compiler/Javac.java b/src/main/javassist/compiler/Javac.java new file mode 100644 index 0000000..9314bbc --- /dev/null +++ b/src/main/javassist/compiler/Javac.java @@ -0,0 +1,609 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.compiler; + +import javassist.CtClass; +import javassist.CtPrimitiveType; +import javassist.CtMember; +import javassist.CtField; +import javassist.CtBehavior; +import javassist.CtMethod; +import javassist.CtConstructor; +import javassist.CannotCompileException; +import javassist.Modifier; +import javassist.bytecode.Bytecode; +import javassist.bytecode.CodeAttribute; +import javassist.bytecode.LocalVariableAttribute; +import javassist.bytecode.BadBytecode; +import javassist.bytecode.Opcode; +import javassist.NotFoundException; + +import javassist.compiler.ast.*; + +public class Javac { + JvstCodeGen gen; + SymbolTable stable; + private Bytecode bytecode; + + public static final String param0Name = "$0"; + public static final String resultVarName = "$_"; + public static final String proceedName = "$proceed"; + + /** + * Constructs a compiler. + * + * @param thisClass the class that a compiled method/field + * belongs to. + */ + public Javac(CtClass thisClass) { + this(new Bytecode(thisClass.getClassFile2().getConstPool(), 0, 0), + thisClass); + } + + /** + * Constructs a compiler. + * The produced bytecode is stored in the <code>Bytecode</code> object + * specified by <code>b</code>. + * + * @param thisClass the class that a compiled method/field + * belongs to. + */ + public Javac(Bytecode b, CtClass thisClass) { + gen = new JvstCodeGen(b, thisClass, thisClass.getClassPool()); + stable = new SymbolTable(); + bytecode = b; + } + + /** + * Returns the produced bytecode. + */ + public Bytecode getBytecode() { return bytecode; } + + /** + * Compiles a method, constructor, or field declaration + * to a class. + * A field declaration can declare only one field. + * + * <p>In a method or constructor body, $0, $1, ... and $_ + * are not available. + * + * @return a <code>CtMethod</code>, <code>CtConstructor</code>, + * or <code>CtField</code> object. + * @see #recordProceed(String,String) + */ + public CtMember compile(String src) throws CompileError { + Parser p = new Parser(new Lex(src)); + ASTList mem = p.parseMember1(stable); + try { + if (mem instanceof FieldDecl) + return compileField((FieldDecl)mem); + else { + CtBehavior cb = compileMethod(p, (MethodDecl)mem); + CtClass decl = cb.getDeclaringClass(); + cb.getMethodInfo2() + .rebuildStackMapIf6(decl.getClassPool(), + decl.getClassFile2()); + return cb; + } + } + catch (BadBytecode bb) { + throw new CompileError(bb.getMessage()); + } + catch (CannotCompileException e) { + throw new CompileError(e.getMessage()); + } + } + + public static class CtFieldWithInit extends CtField { + private ASTree init; + + CtFieldWithInit(CtClass type, String name, CtClass declaring) + throws CannotCompileException + { + super(type, name, declaring); + init = null; + } + + protected void setInit(ASTree i) { init = i; } + + protected ASTree getInitAST() { + return init; + } + } + + private CtField compileField(FieldDecl fd) + throws CompileError, CannotCompileException + { + CtFieldWithInit f; + Declarator d = fd.getDeclarator(); + f = new CtFieldWithInit(gen.resolver.lookupClass(d), + d.getVariable().get(), gen.getThisClass()); + f.setModifiers(MemberResolver.getModifiers(fd.getModifiers())); + if (fd.getInit() != null) + f.setInit(fd.getInit()); + + return f; + } + + private CtBehavior compileMethod(Parser p, MethodDecl md) + throws CompileError + { + int mod = MemberResolver.getModifiers(md.getModifiers()); + CtClass[] plist = gen.makeParamList(md); + CtClass[] tlist = gen.makeThrowsList(md); + recordParams(plist, Modifier.isStatic(mod)); + md = p.parseMethod2(stable, md); + try { + if (md.isConstructor()) { + CtConstructor cons = new CtConstructor(plist, + gen.getThisClass()); + cons.setModifiers(mod); + md.accept(gen); + cons.getMethodInfo().setCodeAttribute( + bytecode.toCodeAttribute()); + cons.setExceptionTypes(tlist); + return cons; + } + else { + Declarator r = md.getReturn(); + CtClass rtype = gen.resolver.lookupClass(r); + recordReturnType(rtype, false); + CtMethod method = new CtMethod(rtype, r.getVariable().get(), + plist, gen.getThisClass()); + method.setModifiers(mod); + gen.setThisMethod(method); + md.accept(gen); + if (md.getBody() != null) + method.getMethodInfo().setCodeAttribute( + bytecode.toCodeAttribute()); + else + method.setModifiers(mod | Modifier.ABSTRACT); + + method.setExceptionTypes(tlist); + return method; + } + } + catch (NotFoundException e) { + throw new CompileError(e.toString()); + } + } + + /** + * Compiles a method (or constructor) body. + * + * @src a single statement or a block. + * If null, this method produces a body returning zero or null. + */ + public Bytecode compileBody(CtBehavior method, String src) + throws CompileError + { + try { + int mod = method.getModifiers(); + recordParams(method.getParameterTypes(), Modifier.isStatic(mod)); + + CtClass rtype; + if (method instanceof CtMethod) { + gen.setThisMethod((CtMethod)method); + rtype = ((CtMethod)method).getReturnType(); + } + else + rtype = CtClass.voidType; + + recordReturnType(rtype, false); + boolean isVoid = rtype == CtClass.voidType; + + if (src == null) + makeDefaultBody(bytecode, rtype); + else { + Parser p = new Parser(new Lex(src)); + SymbolTable stb = new SymbolTable(stable); + Stmnt s = p.parseStatement(stb); + if (p.hasMore()) + throw new CompileError( + "the method/constructor body must be surrounded by {}"); + + boolean callSuper = false; + if (method instanceof CtConstructor) + callSuper = !((CtConstructor)method).isClassInitializer(); + + gen.atMethodBody(s, callSuper, isVoid); + } + + return bytecode; + } + catch (NotFoundException e) { + throw new CompileError(e.toString()); + } + } + + private static void makeDefaultBody(Bytecode b, CtClass type) { + int op; + int value; + if (type instanceof CtPrimitiveType) { + CtPrimitiveType pt = (CtPrimitiveType)type; + op = pt.getReturnOp(); + if (op == Opcode.DRETURN) + value = Opcode.DCONST_0; + else if (op == Opcode.FRETURN) + value = Opcode.FCONST_0; + else if (op == Opcode.LRETURN) + value = Opcode.LCONST_0; + else if (op == Opcode.RETURN) + value = Opcode.NOP; + else + value = Opcode.ICONST_0; + } + else { + op = Opcode.ARETURN; + value = Opcode.ACONST_NULL; + } + + if (value != Opcode.NOP) + b.addOpcode(value); + + b.addOpcode(op); + } + + /** + * Records local variables available at the specified program counter. + * If the LocalVariableAttribute is not available, this method does not + * record any local variable. It only returns false. + * + * @param pc program counter (>= 0) + * @return false if the CodeAttribute does not include a + * LocalVariableAttribute. + */ + public boolean recordLocalVariables(CodeAttribute ca, int pc) + throws CompileError + { + LocalVariableAttribute va + = (LocalVariableAttribute) + ca.getAttribute(LocalVariableAttribute.tag); + if (va == null) + return false; + + int n = va.tableLength(); + for (int i = 0; i < n; ++i) { + int start = va.startPc(i); + int len = va.codeLength(i); + if (start <= pc && pc < start + len) + gen.recordVariable(va.descriptor(i), va.variableName(i), + va.index(i), stable); + } + + return true; + } + + /** + * Records parameter names if the LocalVariableAttribute is available. + * It returns false unless the LocalVariableAttribute is available. + * + * @param numOfLocalVars the number of local variables used + * for storing the parameters. + * @return false if the CodeAttribute does not include a + * LocalVariableAttribute. + */ + public boolean recordParamNames(CodeAttribute ca, int numOfLocalVars) + throws CompileError + { + LocalVariableAttribute va + = (LocalVariableAttribute) + ca.getAttribute(LocalVariableAttribute.tag); + if (va == null) + return false; + + int n = va.tableLength(); + for (int i = 0; i < n; ++i) { + int index = va.index(i); + if (index < numOfLocalVars) + gen.recordVariable(va.descriptor(i), va.variableName(i), + index, stable); + } + + return true; + } + + + /** + * Makes variables $0 (this), $1, $2, ..., and $args represent method + * parameters. $args represents an array of all the parameters. + * It also makes $$ available as a parameter list of method call. + * + * <p>This must be called before calling <code>compileStmnt()</code> and + * <code>compileExpr()</code>. The correct value of + * <code>isStatic</code> must be recorded before compilation. + * <code>maxLocals</code> is updated to include $0,... + */ + public int recordParams(CtClass[] params, boolean isStatic) + throws CompileError + { + return gen.recordParams(params, isStatic, "$", "$args", "$$", stable); + } + + /** + * Makes variables $0, $1, $2, ..., and $args represent method + * parameters. $args represents an array of all the parameters. + * It also makes $$ available as a parameter list of method call. + * $0 can represent a local variable other than THIS (variable 0). + * $class is also made available. + * + * <p>This must be called before calling <code>compileStmnt()</code> and + * <code>compileExpr()</code>. The correct value of + * <code>isStatic</code> must be recorded before compilation. + * <code>maxLocals</code> is updated to include $0,... + * + * @paaram use0 true if $0 is used. + * @param varNo the register number of $0 (use0 is true) + * or $1 (otherwise). + * @param target the type of $0 (it can be null if use0 is false). + * It is used as the name of the type represented + * by $class. + * @param isStatic true if the method in which the compiled bytecode + * is embedded is static. + */ + public int recordParams(String target, CtClass[] params, + boolean use0, int varNo, boolean isStatic) + throws CompileError + { + return gen.recordParams(params, isStatic, "$", "$args", "$$", + use0, varNo, target, stable); + } + + /** + * Sets <code>maxLocals</code> to <code>max</code>. + * This method tells the compiler the local variables that have been + * allocated for the rest of the code. When the compiler needs + * new local variables, the local variables at the index <code>max</code>, + * <code>max + 1</code>, ... are assigned. + * + * <p>This method is indirectly called by <code>recordParams</code>. + */ + public void setMaxLocals(int max) { + gen.setMaxLocals(max); + } + + /** + * Prepares to use cast $r, $w, $_, and $type. + * $type is made to represent the specified return type. + * It also enables to write a return statement with a return value + * for void method. + * + * <p>If the return type is void, ($r) does nothing. + * The type of $_ is java.lang.Object. + * + * @param type the return type. + * @param useResultVar true if $_ is used. + * @return -1 or the variable index assigned to $_. + * @see #recordType(CtClass) + */ + public int recordReturnType(CtClass type, boolean useResultVar) + throws CompileError + { + gen.recordType(type); + return gen.recordReturnType(type, "$r", + (useResultVar ? resultVarName : null), stable); + } + + /** + * Prepares to use $type. Note that recordReturnType() overwrites + * the value of $type. + * + * @param t the type represented by $type. + */ + public void recordType(CtClass t) { + gen.recordType(t); + } + + /** + * Makes the given variable available. + * + * @param type variable type + * @param name variable name + */ + public int recordVariable(CtClass type, String name) + throws CompileError + { + return gen.recordVariable(type, name, stable); + } + + /** + * Prepares to use $proceed(). + * If the return type of $proceed() is void, null is pushed on the + * stack. + * + * @param target an expression specifying the target object. + * if null, "this" is the target. + * @param method the method name. + */ + public void recordProceed(String target, String method) + throws CompileError + { + Parser p = new Parser(new Lex(target)); + final ASTree texpr = p.parseExpression(stable); + final String m = method; + + ProceedHandler h = new ProceedHandler() { + public void doit(JvstCodeGen gen, Bytecode b, ASTList args) + throws CompileError + { + ASTree expr = new Member(m); + if (texpr != null) + expr = Expr.make('.', texpr, expr); + + expr = CallExpr.makeCall(expr, args); + gen.compileExpr(expr); + gen.addNullIfVoid(); + } + + public void setReturnType(JvstTypeChecker check, ASTList args) + throws CompileError + { + ASTree expr = new Member(m); + if (texpr != null) + expr = Expr.make('.', texpr, expr); + + expr = CallExpr.makeCall(expr, args); + expr.accept(check); + check.addNullIfVoid(); + } + }; + + gen.setProceedHandler(h, proceedName); + } + + /** + * Prepares to use $proceed() representing a static method. + * If the return type of $proceed() is void, null is pushed on the + * stack. + * + * @param targetClass the fully-qualified dot-separated name + * of the class declaring the method. + * @param method the method name. + */ + public void recordStaticProceed(String targetClass, String method) + throws CompileError + { + final String c = targetClass; + final String m = method; + + ProceedHandler h = new ProceedHandler() { + public void doit(JvstCodeGen gen, Bytecode b, ASTList args) + throws CompileError + { + Expr expr = Expr.make(TokenId.MEMBER, + new Symbol(c), new Member(m)); + expr = CallExpr.makeCall(expr, args); + gen.compileExpr(expr); + gen.addNullIfVoid(); + } + + public void setReturnType(JvstTypeChecker check, ASTList args) + throws CompileError + { + Expr expr = Expr.make(TokenId.MEMBER, + new Symbol(c), new Member(m)); + expr = CallExpr.makeCall(expr, args); + expr.accept(check); + check.addNullIfVoid(); + } + }; + + gen.setProceedHandler(h, proceedName); + } + + /** + * Prepares to use $proceed() representing a private/super's method. + * If the return type of $proceed() is void, null is pushed on the + * stack. This method is for methods invoked by INVOKESPECIAL. + * + * @param target an expression specifying the target object. + * if null, "this" is the target. + * @param classname the class name declaring the method. + * @param methodname the method name. + * @param descriptor the method descriptor. + */ + public void recordSpecialProceed(String target, String classname, + String methodname, String descriptor) + throws CompileError + { + Parser p = new Parser(new Lex(target)); + final ASTree texpr = p.parseExpression(stable); + final String cname = classname; + final String method = methodname; + final String desc = descriptor; + + ProceedHandler h = new ProceedHandler() { + public void doit(JvstCodeGen gen, Bytecode b, ASTList args) + throws CompileError + { + gen.compileInvokeSpecial(texpr, cname, method, desc, args); + } + + public void setReturnType(JvstTypeChecker c, ASTList args) + throws CompileError + { + c.compileInvokeSpecial(texpr, cname, method, desc, args); + } + + }; + + gen.setProceedHandler(h, proceedName); + } + + /** + * Prepares to use $proceed(). + */ + public void recordProceed(ProceedHandler h) { + gen.setProceedHandler(h, proceedName); + } + + /** + * Compiles a statement (or a block). + * <code>recordParams()</code> must be called before invoking + * this method. + * + * <p>Local variables that are not declared + * in the compiled source text might not be accessible within that + * source text. Fields and method parameters ($0, $1, ..) are available. + */ + public void compileStmnt(String src) throws CompileError { + Parser p = new Parser(new Lex(src)); + SymbolTable stb = new SymbolTable(stable); + while (p.hasMore()) { + Stmnt s = p.parseStatement(stb); + if (s != null) + s.accept(gen); + } + } + + /** + * Compiles an exression. <code>recordParams()</code> must be + * called before invoking this method. + * + * <p>Local variables are not accessible + * within the compiled source text. Fields and method parameters + * ($0, $1, ..) are available if <code>recordParams()</code> + * have been invoked. + */ + public void compileExpr(String src) throws CompileError { + ASTree e = parseExpr(src, stable); + compileExpr(e); + } + + /** + * Parsers an expression. + */ + public static ASTree parseExpr(String src, SymbolTable st) + throws CompileError + { + Parser p = new Parser(new Lex(src)); + return p.parseExpression(st); + } + + /** + * Compiles an exression. <code>recordParams()</code> must be + * called before invoking this method. + * + * <p>Local variables are not accessible + * within the compiled source text. Fields and method parameters + * ($0, $1, ..) are available if <code>recordParams()</code> + * have been invoked. + */ + public void compileExpr(ASTree e) throws CompileError { + if (e != null) + gen.compileExpr(e); + } +} diff --git a/src/main/javassist/compiler/JvstCodeGen.java b/src/main/javassist/compiler/JvstCodeGen.java new file mode 100644 index 0000000..91b0eca --- /dev/null +++ b/src/main/javassist/compiler/JvstCodeGen.java @@ -0,0 +1,709 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.compiler; + +import javassist.*; +import javassist.bytecode.*; +import javassist.compiler.ast.*; + +/* Code generator accepting extended Java syntax for Javassist. + */ + +public class JvstCodeGen extends MemberCodeGen { + String paramArrayName = null; + String paramListName = null; + CtClass[] paramTypeList = null; + private int paramVarBase = 0; // variable index for $0 or $1. + private boolean useParam0 = false; // true if $0 is used. + private String param0Type = null; // JVM name + public static final String sigName = "$sig"; + public static final String dollarTypeName = "$type"; + public static final String clazzName = "$class"; + private CtClass dollarType = null; + CtClass returnType = null; + String returnCastName = null; + private String returnVarName = null; // null if $_ is not used. + public static final String wrapperCastName = "$w"; + String proceedName = null; + public static final String cflowName = "$cflow"; + ProceedHandler procHandler = null; // null if not used. + + public JvstCodeGen(Bytecode b, CtClass cc, ClassPool cp) { + super(b, cc, cp); + setTypeChecker(new JvstTypeChecker(cc, cp, this)); + } + + /* Index of $1. + */ + private int indexOfParam1() { + return paramVarBase + (useParam0 ? 1 : 0); + } + + /* Records a ProceedHandler obejct. + * + * @param name the name of the special method call. + * it is usually $proceed. + */ + public void setProceedHandler(ProceedHandler h, String name) { + proceedName = name; + procHandler = h; + } + + /* If the type of the expression compiled last is void, + * add ACONST_NULL and change exprType, arrayDim, className. + */ + public void addNullIfVoid() { + if (exprType == VOID) { + bytecode.addOpcode(ACONST_NULL); + exprType = CLASS; + arrayDim = 0; + className = jvmJavaLangObject; + } + } + + /* To support $args, $sig, and $type. + * $args is an array of parameter list. + */ + public void atMember(Member mem) throws CompileError { + String name = mem.get(); + if (name.equals(paramArrayName)) { + compileParameterList(bytecode, paramTypeList, indexOfParam1()); + exprType = CLASS; + arrayDim = 1; + className = jvmJavaLangObject; + } + else if (name.equals(sigName)) { + bytecode.addLdc(Descriptor.ofMethod(returnType, paramTypeList)); + bytecode.addInvokestatic("javassist/runtime/Desc", "getParams", + "(Ljava/lang/String;)[Ljava/lang/Class;"); + exprType = CLASS; + arrayDim = 1; + className = "java/lang/Class"; + } + else if (name.equals(dollarTypeName)) { + if (dollarType == null) + throw new CompileError(dollarTypeName + " is not available"); + + bytecode.addLdc(Descriptor.of(dollarType)); + callGetType("getType"); + } + else if (name.equals(clazzName)) { + if (param0Type == null) + throw new CompileError(clazzName + " is not available"); + + bytecode.addLdc(param0Type); + callGetType("getClazz"); + } + else + super.atMember(mem); + } + + private void callGetType(String method) { + bytecode.addInvokestatic("javassist/runtime/Desc", method, + "(Ljava/lang/String;)Ljava/lang/Class;"); + exprType = CLASS; + arrayDim = 0; + className = "java/lang/Class"; + } + + protected void atFieldAssign(Expr expr, int op, ASTree left, + ASTree right, boolean doDup) throws CompileError + { + if (left instanceof Member + && ((Member)left).get().equals(paramArrayName)) { + if (op != '=') + throw new CompileError("bad operator for " + paramArrayName); + + right.accept(this); + if (arrayDim != 1 || exprType != CLASS) + throw new CompileError("invalid type for " + paramArrayName); + + atAssignParamList(paramTypeList, bytecode); + if (!doDup) + bytecode.addOpcode(POP); + } + else + super.atFieldAssign(expr, op, left, right, doDup); + } + + protected void atAssignParamList(CtClass[] params, Bytecode code) + throws CompileError + { + if (params == null) + return; + + int varNo = indexOfParam1(); + int n = params.length; + for (int i = 0; i < n; ++i) { + code.addOpcode(DUP); + code.addIconst(i); + code.addOpcode(AALOAD); + compileUnwrapValue(params[i], code); + code.addStore(varNo, params[i]); + varNo += is2word(exprType, arrayDim) ? 2 : 1; + } + } + + public void atCastExpr(CastExpr expr) throws CompileError { + ASTList classname = expr.getClassName(); + if (classname != null && expr.getArrayDim() == 0) { + ASTree p = classname.head(); + if (p instanceof Symbol && classname.tail() == null) { + String typename = ((Symbol)p).get(); + if (typename.equals(returnCastName)) { + atCastToRtype(expr); + return; + } + else if (typename.equals(wrapperCastName)) { + atCastToWrapper(expr); + return; + } + } + } + + super.atCastExpr(expr); + } + + /** + * Inserts a cast operator to the return type. + * If the return type is void, this does nothing. + */ + protected void atCastToRtype(CastExpr expr) throws CompileError { + expr.getOprand().accept(this); + if (exprType == VOID || isRefType(exprType) || arrayDim > 0) + compileUnwrapValue(returnType, bytecode); + else if (returnType instanceof CtPrimitiveType) { + CtPrimitiveType pt = (CtPrimitiveType)returnType; + int destType = MemberResolver.descToType(pt.getDescriptor()); + atNumCastExpr(exprType, destType); + exprType = destType; + arrayDim = 0; + className = null; + } + else + throw new CompileError("invalid cast"); + } + + protected void atCastToWrapper(CastExpr expr) throws CompileError { + expr.getOprand().accept(this); + if (isRefType(exprType) || arrayDim > 0) + return; // Object type. do nothing. + + CtClass clazz = resolver.lookupClass(exprType, arrayDim, className); + if (clazz instanceof CtPrimitiveType) { + CtPrimitiveType pt = (CtPrimitiveType)clazz; + String wrapper = pt.getWrapperName(); + bytecode.addNew(wrapper); // new <wrapper> + bytecode.addOpcode(DUP); // dup + if (pt.getDataSize() > 1) + bytecode.addOpcode(DUP2_X2); // dup2_x2 + else + bytecode.addOpcode(DUP2_X1); // dup2_x1 + + bytecode.addOpcode(POP2); // pop2 + bytecode.addInvokespecial(wrapper, "<init>", + "(" + pt.getDescriptor() + ")V"); + // invokespecial + exprType = CLASS; + arrayDim = 0; + className = jvmJavaLangObject; + } + } + + /* Delegates to a ProcHandler object if the method call is + * $proceed(). It may process $cflow(). + */ + public void atCallExpr(CallExpr expr) throws CompileError { + ASTree method = expr.oprand1(); + if (method instanceof Member) { + String name = ((Member)method).get(); + if (procHandler != null && name.equals(proceedName)) { + procHandler.doit(this, bytecode, (ASTList)expr.oprand2()); + return; + } + else if (name.equals(cflowName)) { + atCflow((ASTList)expr.oprand2()); + return; + } + } + + super.atCallExpr(expr); + } + + /* To support $cflow(). + */ + protected void atCflow(ASTList cname) throws CompileError { + StringBuffer sbuf = new StringBuffer(); + if (cname == null || cname.tail() != null) + throw new CompileError("bad " + cflowName); + + makeCflowName(sbuf, cname.head()); + String name = sbuf.toString(); + Object[] names = resolver.getClassPool().lookupCflow(name); + if (names == null) + throw new CompileError("no such " + cflowName + ": " + name); + + bytecode.addGetstatic((String)names[0], (String)names[1], + "Ljavassist/runtime/Cflow;"); + bytecode.addInvokevirtual("javassist.runtime.Cflow", + "value", "()I"); + exprType = INT; + arrayDim = 0; + className = null; + } + + /* Syntax: + * + * <cflow> : $cflow '(' <cflow name> ')' + * <cflow name> : <identifier> ('.' <identifier>)* + */ + private static void makeCflowName(StringBuffer sbuf, ASTree name) + throws CompileError + { + if (name instanceof Symbol) { + sbuf.append(((Symbol)name).get()); + return; + } + else if (name instanceof Expr) { + Expr expr = (Expr)name; + if (expr.getOperator() == '.') { + makeCflowName(sbuf, expr.oprand1()); + sbuf.append('.'); + makeCflowName(sbuf, expr.oprand2()); + return; + } + } + + throw new CompileError("bad " + cflowName); + } + + /* To support $$. ($$) is equivalent to ($1, ..., $n). + * It can be used only as a parameter list of method call. + */ + public boolean isParamListName(ASTList args) { + if (paramTypeList != null + && args != null && args.tail() == null) { + ASTree left = args.head(); + return (left instanceof Member + && ((Member)left).get().equals(paramListName)); + } + else + return false; + } + + /* + public int getMethodArgsLength(ASTList args) { + if (!isParamListName(args)) + return super.getMethodArgsLength(args); + + return paramTypeList.length; + } + */ + + public int getMethodArgsLength(ASTList args) { + String pname = paramListName; + int n = 0; + while (args != null) { + ASTree a = args.head(); + if (a instanceof Member && ((Member)a).get().equals(pname)) { + if (paramTypeList != null) + n += paramTypeList.length; + } + else + ++n; + + args = args.tail(); + } + + return n; + } + + public void atMethodArgs(ASTList args, int[] types, int[] dims, + String[] cnames) throws CompileError { + CtClass[] params = paramTypeList; + String pname = paramListName; + int i = 0; + while (args != null) { + ASTree a = args.head(); + if (a instanceof Member && ((Member)a).get().equals(pname)) { + if (params != null) { + int n = params.length; + int regno = indexOfParam1(); + for (int k = 0; k < n; ++k) { + CtClass p = params[k]; + regno += bytecode.addLoad(regno, p); + setType(p); + types[i] = exprType; + dims[i] = arrayDim; + cnames[i] = className; + ++i; + } + } + } + else { + a.accept(this); + types[i] = exprType; + dims[i] = arrayDim; + cnames[i] = className; + ++i; + } + + args = args.tail(); + } + } + + /* + public void atMethodArgs(ASTList args, int[] types, int[] dims, + String[] cnames) throws CompileError { + if (!isParamListName(args)) { + super.atMethodArgs(args, types, dims, cnames); + return; + } + + CtClass[] params = paramTypeList; + if (params == null) + return; + + int n = params.length; + int regno = indexOfParam1(); + for (int i = 0; i < n; ++i) { + CtClass p = params[i]; + regno += bytecode.addLoad(regno, p); + setType(p); + types[i] = exprType; + dims[i] = arrayDim; + cnames[i] = className; + } + } + */ + + /* called by Javac#recordSpecialProceed(). + */ + void compileInvokeSpecial(ASTree target, String classname, + String methodname, String descriptor, + ASTList args) + throws CompileError + { + target.accept(this); + int nargs = getMethodArgsLength(args); + atMethodArgs(args, new int[nargs], new int[nargs], + new String[nargs]); + bytecode.addInvokespecial(classname, methodname, descriptor); + setReturnType(descriptor, false, false); + addNullIfVoid(); + } + + /* + * Makes it valid to write "return <expr>;" for a void method. + */ + protected void atReturnStmnt(Stmnt st) throws CompileError { + ASTree result = st.getLeft(); + if (result != null && returnType == CtClass.voidType) { + compileExpr(result); + if (is2word(exprType, arrayDim)) + bytecode.addOpcode(POP2); + else if (exprType != VOID) + bytecode.addOpcode(POP); + + result = null; + } + + atReturnStmnt2(result); + } + + /** + * Makes a cast to the return type ($r) available. + * It also enables $_. + * + * <p>If the return type is void, ($r) does nothing. + * The type of $_ is java.lang.Object. + * + * @param resultName null if $_ is not used. + * @return -1 or the variable index assigned to $_. + */ + public int recordReturnType(CtClass type, String castName, + String resultName, SymbolTable tbl) throws CompileError + { + returnType = type; + returnCastName = castName; + returnVarName = resultName; + if (resultName == null) + return -1; + else { + int varNo = getMaxLocals(); + int locals = varNo + recordVar(type, resultName, varNo, tbl); + setMaxLocals(locals); + return varNo; + } + } + + /** + * Makes $type available. + */ + public void recordType(CtClass t) { + dollarType = t; + } + + /** + * Makes method parameters $0, $1, ..., $args, $$, and $class available. + * $0 is equivalent to THIS if the method is not static. Otherwise, + * if the method is static, then $0 is not available. + */ + public int recordParams(CtClass[] params, boolean isStatic, + String prefix, String paramVarName, + String paramsName, SymbolTable tbl) + throws CompileError + { + return recordParams(params, isStatic, prefix, paramVarName, + paramsName, !isStatic, 0, getThisName(), tbl); + } + + /** + * Makes method parameters $0, $1, ..., $args, $$, and $class available. + * $0 is available only if use0 is true. It might not be equivalent + * to THIS. + * + * @param params the parameter types (the types of $1, $2, ..) + * @param prefix it must be "$" (the first letter of $0, $1, ...) + * @param paramVarName it must be "$args" + * @param paramsName it must be "$$" + * @param use0 true if $0 is used. + * @param paramBase the register number of $0 (use0 is true) + * or $1 (otherwise). + * @param target the class of $0. If use0 is false, target + * can be null. The value of "target" is also used + * as the name of the type represented by $class. + * @param isStatic true if the method in which the compiled bytecode + * is embedded is static. + */ + public int recordParams(CtClass[] params, boolean isStatic, + String prefix, String paramVarName, + String paramsName, boolean use0, + int paramBase, String target, + SymbolTable tbl) + throws CompileError + { + int varNo; + + paramTypeList = params; + paramArrayName = paramVarName; + paramListName = paramsName; + paramVarBase = paramBase; + useParam0 = use0; + + if (target != null) + param0Type = MemberResolver.jvmToJavaName(target); + + inStaticMethod = isStatic; + varNo = paramBase; + if (use0) { + String varName = prefix + "0"; + Declarator decl + = new Declarator(CLASS, MemberResolver.javaToJvmName(target), + 0, varNo++, new Symbol(varName)); + tbl.append(varName, decl); + } + + for (int i = 0; i < params.length; ++i) + varNo += recordVar(params[i], prefix + (i + 1), varNo, tbl); + + if (getMaxLocals() < varNo) + setMaxLocals(varNo); + + return varNo; + } + + /** + * Makes the given variable name available. + * + * @param type variable type + * @param varName variable name + */ + public int recordVariable(CtClass type, String varName, SymbolTable tbl) + throws CompileError + { + if (varName == null) + return -1; + else { + int varNo = getMaxLocals(); + int locals = varNo + recordVar(type, varName, varNo, tbl); + setMaxLocals(locals); + return varNo; + } + } + + private int recordVar(CtClass cc, String varName, int varNo, + SymbolTable tbl) throws CompileError + { + if (cc == CtClass.voidType) { + exprType = CLASS; + arrayDim = 0; + className = jvmJavaLangObject; + } + else + setType(cc); + + Declarator decl + = new Declarator(exprType, className, arrayDim, + varNo, new Symbol(varName)); + tbl.append(varName, decl); + return is2word(exprType, arrayDim) ? 2 : 1; + } + + /** + * Makes the given variable name available. + * + * @param typeDesc the type descriptor of the variable + * @param varName variable name + * @param varNo an index into the local variable array + */ + public void recordVariable(String typeDesc, String varName, int varNo, + SymbolTable tbl) throws CompileError + { + char c; + int dim = 0; + while ((c = typeDesc.charAt(dim)) == '[') + ++dim; + + int type = MemberResolver.descToType(c); + String cname = null; + if (type == CLASS) { + if (dim == 0) + cname = typeDesc.substring(1, typeDesc.length() - 1); + else + cname = typeDesc.substring(dim + 1, typeDesc.length() - 1); + } + + Declarator decl + = new Declarator(type, cname, dim, varNo, new Symbol(varName)); + tbl.append(varName, decl); + } + + /* compileParameterList() returns the stack size used + * by the produced code. + * + * This method correctly computes the max_stack value. + * + * @param regno the index of the local variable in which + * the first argument is received. + * (0: static method, 1: regular method.) + */ + public static int compileParameterList(Bytecode code, + CtClass[] params, int regno) { + if (params == null) { + code.addIconst(0); // iconst_0 + code.addAnewarray(javaLangObject); // anewarray Object + return 1; + } + else { + CtClass[] args = new CtClass[1]; + int n = params.length; + code.addIconst(n); // iconst_<n> + code.addAnewarray(javaLangObject); // anewarray Object + for (int i = 0; i < n; ++i) { + code.addOpcode(Bytecode.DUP); // dup + code.addIconst(i); // iconst_<i> + if (params[i].isPrimitive()) { + CtPrimitiveType pt = (CtPrimitiveType)params[i]; + String wrapper = pt.getWrapperName(); + code.addNew(wrapper); // new <wrapper> + code.addOpcode(Bytecode.DUP); // dup + int s = code.addLoad(regno, pt); // ?load <regno> + regno += s; + args[0] = pt; + code.addInvokespecial(wrapper, "<init>", + Descriptor.ofMethod(CtClass.voidType, args)); + // invokespecial + } + else { + code.addAload(regno); // aload <regno> + ++regno; + } + + code.addOpcode(Bytecode.AASTORE); // aastore + } + + return 8; + } + } + + protected void compileUnwrapValue(CtClass type, Bytecode code) + throws CompileError + { + if (type == CtClass.voidType) { + addNullIfVoid(); + return; + } + + if (exprType == VOID) + throw new CompileError("invalid type for " + returnCastName); + + if (type instanceof CtPrimitiveType) { + CtPrimitiveType pt = (CtPrimitiveType)type; + // pt is not voidType. + String wrapper = pt.getWrapperName(); + code.addCheckcast(wrapper); + code.addInvokevirtual(wrapper, pt.getGetMethodName(), + pt.getGetMethodDescriptor()); + setType(type); + } + else { + code.addCheckcast(type); + setType(type); + } + } + + /* Sets exprType, arrayDim, and className; + * If type is void, then this method does nothing. + */ + public void setType(CtClass type) throws CompileError { + setType(type, 0); + } + + private void setType(CtClass type, int dim) throws CompileError { + if (type.isPrimitive()) { + CtPrimitiveType pt = (CtPrimitiveType)type; + exprType = MemberResolver.descToType(pt.getDescriptor()); + arrayDim = dim; + className = null; + } + else if (type.isArray()) + try { + setType(type.getComponentType(), dim + 1); + } + catch (NotFoundException e) { + throw new CompileError("undefined type: " + type.getName()); + } + else { + exprType = CLASS; + arrayDim = dim; + className = MemberResolver.javaToJvmName(type.getName()); + } + } + + /* Performs implicit coercion from exprType to type. + */ + public void doNumCast(CtClass type) throws CompileError { + if (arrayDim == 0 && !isRefType(exprType)) + if (type instanceof CtPrimitiveType) { + CtPrimitiveType pt = (CtPrimitiveType)type; + atNumCastExpr(exprType, + MemberResolver.descToType(pt.getDescriptor())); + } + else + throw new CompileError("type mismatch"); + } +} diff --git a/src/main/javassist/compiler/JvstTypeChecker.java b/src/main/javassist/compiler/JvstTypeChecker.java new file mode 100644 index 0000000..d88909e --- /dev/null +++ b/src/main/javassist/compiler/JvstTypeChecker.java @@ -0,0 +1,281 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.compiler; + +import javassist.*; +import javassist.compiler.ast.*; + +/* Type checker accepting extended Java syntax for Javassist. + */ + +public class JvstTypeChecker extends TypeChecker { + private JvstCodeGen codeGen; + + public JvstTypeChecker(CtClass cc, ClassPool cp, JvstCodeGen gen) { + super(cc, cp); + codeGen = gen; + } + + /* If the type of the expression compiled last is void, + * add ACONST_NULL and change exprType, arrayDim, className. + */ + public void addNullIfVoid() { + if (exprType == VOID) { + exprType = CLASS; + arrayDim = 0; + className = jvmJavaLangObject; + } + } + + /* To support $args, $sig, and $type. + * $args is an array of parameter list. + */ + public void atMember(Member mem) throws CompileError { + String name = mem.get(); + if (name.equals(codeGen.paramArrayName)) { + exprType = CLASS; + arrayDim = 1; + className = jvmJavaLangObject; + } + else if (name.equals(JvstCodeGen.sigName)) { + exprType = CLASS; + arrayDim = 1; + className = "java/lang/Class"; + } + else if (name.equals(JvstCodeGen.dollarTypeName) + || name.equals(JvstCodeGen.clazzName)) { + exprType = CLASS; + arrayDim = 0; + className = "java/lang/Class"; + } + else + super.atMember(mem); + } + + protected void atFieldAssign(Expr expr, int op, ASTree left, ASTree right) + throws CompileError + { + if (left instanceof Member + && ((Member)left).get().equals(codeGen.paramArrayName)) { + right.accept(this); + CtClass[] params = codeGen.paramTypeList; + if (params == null) + return; + + int n = params.length; + for (int i = 0; i < n; ++i) + compileUnwrapValue(params[i]); + } + else + super.atFieldAssign(expr, op, left, right); + } + + public void atCastExpr(CastExpr expr) throws CompileError { + ASTList classname = expr.getClassName(); + if (classname != null && expr.getArrayDim() == 0) { + ASTree p = classname.head(); + if (p instanceof Symbol && classname.tail() == null) { + String typename = ((Symbol)p).get(); + if (typename.equals(codeGen.returnCastName)) { + atCastToRtype(expr); + return; + } + else if (typename.equals(JvstCodeGen.wrapperCastName)) { + atCastToWrapper(expr); + return; + } + } + } + + super.atCastExpr(expr); + } + + /** + * Inserts a cast operator to the return type. + * If the return type is void, this does nothing. + */ + protected void atCastToRtype(CastExpr expr) throws CompileError { + CtClass returnType = codeGen.returnType; + expr.getOprand().accept(this); + if (exprType == VOID || CodeGen.isRefType(exprType) || arrayDim > 0) + compileUnwrapValue(returnType); + else if (returnType instanceof CtPrimitiveType) { + CtPrimitiveType pt = (CtPrimitiveType)returnType; + int destType = MemberResolver.descToType(pt.getDescriptor()); + exprType = destType; + arrayDim = 0; + className = null; + } + } + + protected void atCastToWrapper(CastExpr expr) throws CompileError { + expr.getOprand().accept(this); + if (CodeGen.isRefType(exprType) || arrayDim > 0) + return; // Object type. do nothing. + + CtClass clazz = resolver.lookupClass(exprType, arrayDim, className); + if (clazz instanceof CtPrimitiveType) { + exprType = CLASS; + arrayDim = 0; + className = jvmJavaLangObject; + } + } + + /* Delegates to a ProcHandler object if the method call is + * $proceed(). It may process $cflow(). + */ + public void atCallExpr(CallExpr expr) throws CompileError { + ASTree method = expr.oprand1(); + if (method instanceof Member) { + String name = ((Member)method).get(); + if (codeGen.procHandler != null + && name.equals(codeGen.proceedName)) { + codeGen.procHandler.setReturnType(this, + (ASTList)expr.oprand2()); + return; + } + else if (name.equals(JvstCodeGen.cflowName)) { + atCflow((ASTList)expr.oprand2()); + return; + } + } + + super.atCallExpr(expr); + } + + /* To support $cflow(). + */ + protected void atCflow(ASTList cname) throws CompileError { + exprType = INT; + arrayDim = 0; + className = null; + } + + /* To support $$. ($$) is equivalent to ($1, ..., $n). + * It can be used only as a parameter list of method call. + */ + public boolean isParamListName(ASTList args) { + if (codeGen.paramTypeList != null + && args != null && args.tail() == null) { + ASTree left = args.head(); + return (left instanceof Member + && ((Member)left).get().equals(codeGen.paramListName)); + } + else + return false; + } + + public int getMethodArgsLength(ASTList args) { + String pname = codeGen.paramListName; + int n = 0; + while (args != null) { + ASTree a = args.head(); + if (a instanceof Member && ((Member)a).get().equals(pname)) { + if (codeGen.paramTypeList != null) + n += codeGen.paramTypeList.length; + } + else + ++n; + + args = args.tail(); + } + + return n; + } + + public void atMethodArgs(ASTList args, int[] types, int[] dims, + String[] cnames) throws CompileError { + CtClass[] params = codeGen.paramTypeList; + String pname = codeGen.paramListName; + int i = 0; + while (args != null) { + ASTree a = args.head(); + if (a instanceof Member && ((Member)a).get().equals(pname)) { + if (params != null) { + int n = params.length; + for (int k = 0; k < n; ++k) { + CtClass p = params[k]; + setType(p); + types[i] = exprType; + dims[i] = arrayDim; + cnames[i] = className; + ++i; + } + } + } + else { + a.accept(this); + types[i] = exprType; + dims[i] = arrayDim; + cnames[i] = className; + ++i; + } + + args = args.tail(); + } + } + + /* called by Javac#recordSpecialProceed(). + */ + void compileInvokeSpecial(ASTree target, String classname, + String methodname, String descriptor, + ASTList args) + throws CompileError + { + target.accept(this); + int nargs = getMethodArgsLength(args); + atMethodArgs(args, new int[nargs], new int[nargs], + new String[nargs]); + setReturnType(descriptor); + addNullIfVoid(); + } + + protected void compileUnwrapValue(CtClass type) throws CompileError + { + if (type == CtClass.voidType) + addNullIfVoid(); + else + setType(type); + } + + /* Sets exprType, arrayDim, and className; + * If type is void, then this method does nothing. + */ + public void setType(CtClass type) throws CompileError { + setType(type, 0); + } + + private void setType(CtClass type, int dim) throws CompileError { + if (type.isPrimitive()) { + CtPrimitiveType pt = (CtPrimitiveType)type; + exprType = MemberResolver.descToType(pt.getDescriptor()); + arrayDim = dim; + className = null; + } + else if (type.isArray()) + try { + setType(type.getComponentType(), dim + 1); + } + catch (NotFoundException e) { + throw new CompileError("undefined type: " + type.getName()); + } + else { + exprType = CLASS; + arrayDim = dim; + className = MemberResolver.javaToJvmName(type.getName()); + } + } +} diff --git a/src/main/javassist/compiler/KeywordTable.java b/src/main/javassist/compiler/KeywordTable.java new file mode 100644 index 0000000..3a22a79 --- /dev/null +++ b/src/main/javassist/compiler/KeywordTable.java @@ -0,0 +1,32 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.compiler; + +public final class KeywordTable extends java.util.HashMap { + public KeywordTable() { super(); } + + public int lookup(String name) { + Object found = get(name); + if (found == null) + return -1; + else + return ((Integer)found).intValue(); + } + + public void append(String name, int t) { + put(name, new Integer(t)); + } +} diff --git a/src/main/javassist/compiler/Lex.java b/src/main/javassist/compiler/Lex.java new file mode 100644 index 0000000..5233a25 --- /dev/null +++ b/src/main/javassist/compiler/Lex.java @@ -0,0 +1,550 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.compiler; + +class Token { + public Token next = null; + public int tokenId; + + public long longValue; + public double doubleValue; + public String textValue; +} + +public class Lex implements TokenId { + private int lastChar; + private StringBuffer textBuffer; + private Token currentToken; + private Token lookAheadTokens; + + private String input; + private int position, maxlen, lineNumber; + + /** + * Constructs a lexical analyzer. + */ + public Lex(String s) { + lastChar = -1; + textBuffer = new StringBuffer(); + currentToken = new Token(); + lookAheadTokens = null; + + input = s; + position = 0; + maxlen = s.length(); + lineNumber = 0; + } + + public int get() { + if (lookAheadTokens == null) + return get(currentToken); + else { + Token t; + currentToken = t = lookAheadTokens; + lookAheadTokens = lookAheadTokens.next; + return t.tokenId; + } + } + + /** + * Looks at the next token. + */ + public int lookAhead() { + return lookAhead(0); + } + + public int lookAhead(int i) { + Token tk = lookAheadTokens; + if (tk == null) { + lookAheadTokens = tk = currentToken; // reuse an object! + tk.next = null; + get(tk); + } + + for (; i-- > 0; tk = tk.next) + if (tk.next == null) { + Token tk2; + tk.next = tk2 = new Token(); + get(tk2); + } + + currentToken = tk; + return tk.tokenId; + } + + public String getString() { + return currentToken.textValue; + } + + public long getLong() { + return currentToken.longValue; + } + + public double getDouble() { + return currentToken.doubleValue; + } + + private int get(Token token) { + int t; + do { + t = readLine(token); + } while (t == '\n'); + token.tokenId = t; + return t; + } + + private int readLine(Token token) { + int c = getNextNonWhiteChar(); + if(c < 0) + return c; + else if(c == '\n') { + ++lineNumber; + return '\n'; + } + else if (c == '\'') + return readCharConst(token); + else if (c == '"') + return readStringL(token); + else if ('0' <= c && c <= '9') + return readNumber(c, token); + else if(c == '.'){ + c = getc(); + if ('0' <= c && c <= '9') { + StringBuffer tbuf = textBuffer; + tbuf.setLength(0); + tbuf.append('.'); + return readDouble(tbuf, c, token); + } + else{ + ungetc(c); + return readSeparator('.'); + } + } + else if (Character.isJavaIdentifierStart((char)c)) + return readIdentifier(c, token); + else + return readSeparator(c); + } + + private int getNextNonWhiteChar() { + int c; + do { + c = getc(); + if (c == '/') { + c = getc(); + if (c == '/') + do { + c = getc(); + } while (c != '\n' && c != '\r' && c != -1); + else if (c == '*') + while (true) { + c = getc(); + if (c == -1) + break; + else if (c == '*') + if ((c = getc()) == '/') { + c = ' '; + break; + } + else + ungetc(c); + } + else { + ungetc(c); + c = '/'; + } + } + } while(isBlank(c)); + return c; + } + + private int readCharConst(Token token) { + int c; + int value = 0; + while ((c = getc()) != '\'') + if (c == '\\') + value = readEscapeChar(); + else if (c < 0x20) { + if (c == '\n') + ++lineNumber; + + return BadToken; + } + else + value = c; + + token.longValue = value; + return CharConstant; + } + + private int readEscapeChar() { + int c = getc(); + if (c == 'n') + c = '\n'; + else if (c == 't') + c = '\t'; + else if (c == 'r') + c = '\r'; + else if (c == 'f') + c = '\f'; + else if (c == '\n') + ++lineNumber; + + return c; + } + + private int readStringL(Token token) { + int c; + StringBuffer tbuf = textBuffer; + tbuf.setLength(0); + for (;;) { + while ((c = getc()) != '"') { + if (c == '\\') + c = readEscapeChar(); + else if (c == '\n' || c < 0) { + ++lineNumber; + return BadToken; + } + + tbuf.append((char)c); + } + + for (;;) { + c = getc(); + if (c == '\n') + ++lineNumber; + else if (!isBlank(c)) + break; + } + + if (c != '"') { + ungetc(c); + break; + } + } + + token.textValue = tbuf.toString(); + return StringL; + } + + private int readNumber(int c, Token token) { + long value = 0; + int c2 = getc(); + if (c == '0') + if (c2 == 'X' || c2 == 'x') + for (;;) { + c = getc(); + if ('0' <= c && c <= '9') + value = value * 16 + (long)(c - '0'); + else if ('A' <= c && c <= 'F') + value = value * 16 + (long)(c - 'A' + 10); + else if ('a' <= c && c <= 'f') + value = value * 16 + (long)(c - 'a' + 10); + else { + token.longValue = value; + if (c == 'L' || c == 'l') + return LongConstant; + else { + ungetc(c); + return IntConstant; + } + } + } + else if ('0' <= c2 && c2 <= '7') { + value = c2 - '0'; + for (;;) { + c = getc(); + if ('0' <= c && c <= '7') + value = value * 8 + (long)(c - '0'); + else { + token.longValue = value; + if (c == 'L' || c == 'l') + return LongConstant; + else { + ungetc(c); + return IntConstant; + } + } + } + } + + value = c - '0'; + while ('0' <= c2 && c2 <= '9') { + value = value * 10 + c2 - '0'; + c2 = getc(); + } + + token.longValue = value; + if (c2 == 'F' || c2 == 'f') { + token.doubleValue = (double)value; + return FloatConstant; + } + else if (c2 == 'E' || c2 == 'e' + || c2 == 'D' || c2 == 'd' || c2 == '.') { + StringBuffer tbuf = textBuffer; + tbuf.setLength(0); + tbuf.append(value); + return readDouble(tbuf, c2, token); + } + else if (c2 == 'L' || c2 == 'l') + return LongConstant; + else { + ungetc(c2); + return IntConstant; + } + } + + private int readDouble(StringBuffer sbuf, int c, Token token) { + if (c != 'E' && c != 'e' && c != 'D' && c != 'd') { + sbuf.append((char)c); + for (;;) { + c = getc(); + if ('0' <= c && c <= '9') + sbuf.append((char)c); + else + break; + } + } + + if (c == 'E' || c == 'e') { + sbuf.append((char)c); + c = getc(); + if (c == '+' || c == '-') { + sbuf.append((char)c); + c = getc(); + } + + while ('0' <= c && c <= '9') { + sbuf.append((char)c); + c = getc(); + } + } + + try { + token.doubleValue = Double.parseDouble(sbuf.toString()); + } + catch (NumberFormatException e) { + return BadToken; + } + + if (c == 'F' || c == 'f') + return FloatConstant; + else { + if (c != 'D' && c != 'd') + ungetc(c); + + return DoubleConstant; + } + } + + // !"#$%&'( )*+,-./0 12345678 9:;<=>? + private static final int[] equalOps + = { NEQ, 0, 0, 0, MOD_E, AND_E, 0, 0, + 0, MUL_E, PLUS_E, 0, MINUS_E, 0, DIV_E, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, LE, EQ, GE, 0 }; + + private int readSeparator(int c) { + int c2, c3; + if ('!' <= c && c <= '?') { + int t = equalOps[c - '!']; + if (t == 0) + return c; + else { + c2 = getc(); + if (c == c2) + switch (c) { + case '=' : + return EQ; + case '+' : + return PLUSPLUS; + case '-' : + return MINUSMINUS; + case '&' : + return ANDAND; + case '<' : + c3 = getc(); + if (c3 == '=') + return LSHIFT_E; + else { + ungetc(c3); + return LSHIFT; + } + case '>' : + c3 = getc(); + if (c3 == '=') + return RSHIFT_E; + else if (c3 == '>') { + c3 = getc(); + if (c3 == '=') + return ARSHIFT_E; + else { + ungetc(c3); + return ARSHIFT; + } + } + else { + ungetc(c3); + return RSHIFT; + } + default : + break; + } + else if (c2 == '=') + return t; + } + } + else if (c == '^') { + c2 = getc(); + if (c2 == '=') + return EXOR_E; + } + else if (c == '|') { + c2 = getc(); + if (c2 == '=') + return OR_E; + else if (c2 == '|') + return OROR; + } + else + return c; + + ungetc(c2); + return c; + } + + private int readIdentifier(int c, Token token) { + StringBuffer tbuf = textBuffer; + tbuf.setLength(0); + + do { + tbuf.append((char)c); + c = getc(); + } while (Character.isJavaIdentifierPart((char)c)); + + ungetc(c); + + String name = tbuf.toString(); + int t = ktable.lookup(name); + if (t >= 0) + return t; + else { + /* tbuf.toString() is executed quickly since it does not + * need memory copy. Using a hand-written extensible + * byte-array class instead of StringBuffer is not a good idea + * for execution speed. Converting a byte array to a String + * object is very slow. Using an extensible char array + * might be OK. + */ + token.textValue = name; + return Identifier; + } + } + + private static final KeywordTable ktable = new KeywordTable(); + + static { + ktable.append("abstract", ABSTRACT); + ktable.append("boolean", BOOLEAN); + ktable.append("break", BREAK); + ktable.append("byte", BYTE); + ktable.append("case", CASE); + ktable.append("catch", CATCH); + ktable.append("char", CHAR); + ktable.append("class", CLASS); + ktable.append("const", CONST); + ktable.append("continue", CONTINUE); + ktable.append("default", DEFAULT); + ktable.append("do", DO); + ktable.append("double", DOUBLE); + ktable.append("else", ELSE); + ktable.append("extends", EXTENDS); + ktable.append("false", FALSE); + ktable.append("final", FINAL); + ktable.append("finally", FINALLY); + ktable.append("float", FLOAT); + ktable.append("for", FOR); + ktable.append("goto", GOTO); + ktable.append("if", IF); + ktable.append("implements", IMPLEMENTS); + ktable.append("import", IMPORT); + ktable.append("instanceof", INSTANCEOF); + ktable.append("int", INT); + ktable.append("interface", INTERFACE); + ktable.append("long", LONG); + ktable.append("native", NATIVE); + ktable.append("new", NEW); + ktable.append("null", NULL); + ktable.append("package", PACKAGE); + ktable.append("private", PRIVATE); + ktable.append("protected", PROTECTED); + ktable.append("public", PUBLIC); + ktable.append("return", RETURN); + ktable.append("short", SHORT); + ktable.append("static", STATIC); + ktable.append("strictfp", STRICT); + ktable.append("super", SUPER); + ktable.append("switch", SWITCH); + ktable.append("synchronized", SYNCHRONIZED); + ktable.append("this", THIS); + ktable.append("throw", THROW); + ktable.append("throws", THROWS); + ktable.append("transient", TRANSIENT); + ktable.append("true", TRUE); + ktable.append("try", TRY); + ktable.append("void", VOID); + ktable.append("volatile", VOLATILE); + ktable.append("while", WHILE); + } + + private static boolean isBlank(int c) { + return c == ' ' || c == '\t' || c == '\f' || c == '\r' + || c == '\n'; + } + + private static boolean isDigit(int c) { + return '0' <= c && c <= '9'; + } + + private void ungetc(int c) { + lastChar = c; + } + + public String getTextAround() { + int begin = position - 10; + if (begin < 0) + begin = 0; + + int end = position + 10; + if (end > maxlen) + end = maxlen; + + return input.substring(begin, end); + } + + private int getc() { + if (lastChar < 0) + if (position < maxlen) + return input.charAt(position++); + else + return -1; + else { + int c = lastChar; + lastChar = -1; + return c; + } + } +} diff --git a/src/main/javassist/compiler/MemberCodeGen.java b/src/main/javassist/compiler/MemberCodeGen.java new file mode 100644 index 0000000..cbff0a4 --- /dev/null +++ b/src/main/javassist/compiler/MemberCodeGen.java @@ -0,0 +1,1148 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.compiler; + +import javassist.*; +import javassist.bytecode.*; +import javassist.compiler.ast.*; + +import java.util.ArrayList; + +/* Code generator methods depending on javassist.* classes. + */ +public class MemberCodeGen extends CodeGen { + protected MemberResolver resolver; + protected CtClass thisClass; + protected MethodInfo thisMethod; + + protected boolean resultStatic; + + public MemberCodeGen(Bytecode b, CtClass cc, ClassPool cp) { + super(b); + resolver = new MemberResolver(cp); + thisClass = cc; + thisMethod = null; + } + + /** + * Returns the major version of the class file + * targeted by this compilation. + */ + public int getMajorVersion() { + ClassFile cf = thisClass.getClassFile2(); + if (cf == null) + return ClassFile.MAJOR_VERSION; // JDK 1.3 + else + return cf.getMajorVersion(); + } + + /** + * Records the currently compiled method. + */ + public void setThisMethod(CtMethod m) { + thisMethod = m.getMethodInfo2(); + if (typeChecker != null) + typeChecker.setThisMethod(thisMethod); + } + + public CtClass getThisClass() { return thisClass; } + + /** + * Returns the JVM-internal representation of this class name. + */ + protected String getThisName() { + return MemberResolver.javaToJvmName(thisClass.getName()); + } + + /** + * Returns the JVM-internal representation of this super class name. + */ + protected String getSuperName() throws CompileError { + return MemberResolver.javaToJvmName( + MemberResolver.getSuperclass(thisClass).getName()); + } + + protected void insertDefaultSuperCall() throws CompileError { + bytecode.addAload(0); + bytecode.addInvokespecial(MemberResolver.getSuperclass(thisClass), + "<init>", "()V"); + } + + static class JsrHook extends ReturnHook { + ArrayList jsrList; + CodeGen cgen; + int var; + + JsrHook(CodeGen gen) { + super(gen); + jsrList = new ArrayList(); + cgen = gen; + var = -1; + } + + private int getVar(int size) { + if (var < 0) { + var = cgen.getMaxLocals(); + cgen.incMaxLocals(size); + } + + return var; + } + + private void jsrJmp(Bytecode b) { + b.addOpcode(Opcode.GOTO); + jsrList.add(new int[] {b.currentPc(), var}); + b.addIndex(0); + } + + protected boolean doit(Bytecode b, int opcode) { + switch (opcode) { + case Opcode.RETURN : + jsrJmp(b); + break; + case ARETURN : + b.addAstore(getVar(1)); + jsrJmp(b); + b.addAload(var); + break; + case IRETURN : + b.addIstore(getVar(1)); + jsrJmp(b); + b.addIload(var); + break; + case LRETURN : + b.addLstore(getVar(2)); + jsrJmp(b); + b.addLload(var); + break; + case DRETURN : + b.addDstore(getVar(2)); + jsrJmp(b); + b.addDload(var); + break; + case FRETURN : + b.addFstore(getVar(1)); + jsrJmp(b); + b.addFload(var); + break; + default : + throw new RuntimeException("fatal"); + } + + return false; + } + } + + static class JsrHook2 extends ReturnHook { + int var; + int target; + + JsrHook2(CodeGen gen, int[] retTarget) { + super(gen); + target = retTarget[0]; + var = retTarget[1]; + } + + protected boolean doit(Bytecode b, int opcode) { + switch (opcode) { + case Opcode.RETURN : + break; + case ARETURN : + b.addAstore(var); + break; + case IRETURN : + b.addIstore(var); + break; + case LRETURN : + b.addLstore(var); + break; + case DRETURN : + b.addDstore(var); + break; + case FRETURN : + b.addFstore(var); + break; + default : + throw new RuntimeException("fatal"); + } + + b.addOpcode(Opcode.GOTO); + b.addIndex(target - b.currentPc() + 3); + return true; + } + } + + protected void atTryStmnt(Stmnt st) throws CompileError { + Bytecode bc = bytecode; + Stmnt body = (Stmnt)st.getLeft(); + if (body == null) + return; + + ASTList catchList = (ASTList)st.getRight().getLeft(); + Stmnt finallyBlock = (Stmnt)st.getRight().getRight().getLeft(); + ArrayList gotoList = new ArrayList(); + + JsrHook jsrHook = null; + if (finallyBlock != null) + jsrHook = new JsrHook(this); + + int start = bc.currentPc(); + body.accept(this); + int end = bc.currentPc(); + if (start == end) + throw new CompileError("empty try block"); + + boolean tryNotReturn = !hasReturned; + if (tryNotReturn) { + bc.addOpcode(Opcode.GOTO); + gotoList.add(new Integer(bc.currentPc())); + bc.addIndex(0); // correct later + } + + int var = getMaxLocals(); + incMaxLocals(1); + while (catchList != null) { + // catch clause + Pair p = (Pair)catchList.head(); + catchList = catchList.tail(); + Declarator decl = (Declarator)p.getLeft(); + Stmnt block = (Stmnt)p.getRight(); + + decl.setLocalVar(var); + + CtClass type = resolver.lookupClassByJvmName(decl.getClassName()); + decl.setClassName(MemberResolver.javaToJvmName(type.getName())); + bc.addExceptionHandler(start, end, bc.currentPc(), type); + bc.growStack(1); + bc.addAstore(var); + hasReturned = false; + if (block != null) + block.accept(this); + + if (!hasReturned) { + bc.addOpcode(Opcode.GOTO); + gotoList.add(new Integer(bc.currentPc())); + bc.addIndex(0); // correct later + tryNotReturn = true; + } + } + + if (finallyBlock != null) { + jsrHook.remove(this); + // catch (any) clause + int pcAnyCatch = bc.currentPc(); + bc.addExceptionHandler(start, pcAnyCatch, pcAnyCatch, 0); + bc.growStack(1); + bc.addAstore(var); + hasReturned = false; + finallyBlock.accept(this); + if (!hasReturned) { + bc.addAload(var); + bc.addOpcode(ATHROW); + } + + addFinally(jsrHook.jsrList, finallyBlock); + } + + int pcEnd = bc.currentPc(); + patchGoto(gotoList, pcEnd); + hasReturned = !tryNotReturn; + if (finallyBlock != null) { + if (tryNotReturn) + finallyBlock.accept(this); + } + } + + /** + * Adds a finally clause for earch return statement. + */ + private void addFinally(ArrayList returnList, Stmnt finallyBlock) + throws CompileError + { + Bytecode bc = bytecode; + int n = returnList.size(); + for (int i = 0; i < n; ++i) { + final int[] ret = (int[])returnList.get(i); + int pc = ret[0]; + bc.write16bit(pc, bc.currentPc() - pc + 1); + ReturnHook hook = new JsrHook2(this, ret); + finallyBlock.accept(this); + hook.remove(this); + if (!hasReturned) { + bc.addOpcode(Opcode.GOTO); + bc.addIndex(pc + 3 - bc.currentPc()); + } + } + } + + public void atNewExpr(NewExpr expr) throws CompileError { + if (expr.isArray()) + atNewArrayExpr(expr); + else { + CtClass clazz = resolver.lookupClassByName(expr.getClassName()); + String cname = clazz.getName(); + ASTList args = expr.getArguments(); + bytecode.addNew(cname); + bytecode.addOpcode(DUP); + + atMethodCallCore(clazz, MethodInfo.nameInit, args, + false, true, -1, null); + + exprType = CLASS; + arrayDim = 0; + className = MemberResolver.javaToJvmName(cname); + } + } + + public void atNewArrayExpr(NewExpr expr) throws CompileError { + int type = expr.getArrayType(); + ASTList size = expr.getArraySize(); + ASTList classname = expr.getClassName(); + ArrayInit init = expr.getInitializer(); + if (size.length() > 1) { + if (init != null) + throw new CompileError( + "sorry, multi-dimensional array initializer " + + "for new is not supported"); + + atMultiNewArray(type, classname, size); + return; + } + + ASTree sizeExpr = size.head(); + atNewArrayExpr2(type, sizeExpr, Declarator.astToClassName(classname, '/'), init); + } + + private void atNewArrayExpr2(int type, ASTree sizeExpr, + String jvmClassname, ArrayInit init) throws CompileError { + if (init == null) + if (sizeExpr == null) + throw new CompileError("no array size"); + else + sizeExpr.accept(this); + else + if (sizeExpr == null) { + int s = init.length(); + bytecode.addIconst(s); + } + else + throw new CompileError("unnecessary array size specified for new"); + + String elementClass; + if (type == CLASS) { + elementClass = resolveClassName(jvmClassname); + bytecode.addAnewarray(MemberResolver.jvmToJavaName(elementClass)); + } + else { + elementClass = null; + int atype = 0; + switch (type) { + case BOOLEAN : + atype = T_BOOLEAN; + break; + case CHAR : + atype = T_CHAR; + break; + case FLOAT : + atype = T_FLOAT; + break; + case DOUBLE : + atype = T_DOUBLE; + break; + case BYTE : + atype = T_BYTE; + break; + case SHORT : + atype = T_SHORT; + break; + case INT : + atype = T_INT; + break; + case LONG : + atype = T_LONG; + break; + default : + badNewExpr(); + break; + } + + bytecode.addOpcode(NEWARRAY); + bytecode.add(atype); + } + + if (init != null) { + int s = init.length(); + ASTList list = init; + for (int i = 0; i < s; i++) { + bytecode.addOpcode(DUP); + bytecode.addIconst(i); + list.head().accept(this); + if (!isRefType(type)) + atNumCastExpr(exprType, type); + + bytecode.addOpcode(getArrayWriteOp(type, 0)); + list = list.tail(); + } + } + + exprType = type; + arrayDim = 1; + className = elementClass; + } + + private static void badNewExpr() throws CompileError { + throw new CompileError("bad new expression"); + } + + protected void atArrayVariableAssign(ArrayInit init, int varType, + int varArray, String varClass) throws CompileError { + atNewArrayExpr2(varType, null, varClass, init); + } + + public void atArrayInit(ArrayInit init) throws CompileError { + throw new CompileError("array initializer is not supported"); + } + + protected void atMultiNewArray(int type, ASTList classname, ASTList size) + throws CompileError + { + int count, dim; + dim = size.length(); + for (count = 0; size != null; size = size.tail()) { + ASTree s = size.head(); + if (s == null) + break; // int[][][] a = new int[3][4][]; + + ++count; + s.accept(this); + if (exprType != INT) + throw new CompileError("bad type for array size"); + } + + String desc; + exprType = type; + arrayDim = dim; + if (type == CLASS) { + className = resolveClassName(classname); + desc = toJvmArrayName(className, dim); + } + else + desc = toJvmTypeName(type, dim); + + bytecode.addMultiNewarray(desc, count); + } + + public void atCallExpr(CallExpr expr) throws CompileError { + String mname = null; + CtClass targetClass = null; + ASTree method = expr.oprand1(); + ASTList args = (ASTList)expr.oprand2(); + boolean isStatic = false; + boolean isSpecial = false; + int aload0pos = -1; + + MemberResolver.Method cached = expr.getMethod(); + if (method instanceof Member) { + mname = ((Member)method).get(); + targetClass = thisClass; + if (inStaticMethod || (cached != null && cached.isStatic())) + isStatic = true; // should be static + else { + aload0pos = bytecode.currentPc(); + bytecode.addAload(0); // this + } + } + else if (method instanceof Keyword) { // constructor + isSpecial = true; + mname = MethodInfo.nameInit; // <init> + targetClass = thisClass; + if (inStaticMethod) + throw new CompileError("a constructor cannot be static"); + else + bytecode.addAload(0); // this + + if (((Keyword)method).get() == SUPER) + targetClass = MemberResolver.getSuperclass(targetClass); + } + else if (method instanceof Expr) { + Expr e = (Expr)method; + mname = ((Symbol)e.oprand2()).get(); + int op = e.getOperator(); + if (op == MEMBER) { // static method + targetClass + = resolver.lookupClass(((Symbol)e.oprand1()).get(), false); + isStatic = true; + } + else if (op == '.') { + ASTree target = e.oprand1(); + if (target instanceof Keyword) + if (((Keyword)target).get() == SUPER) + isSpecial = true; + + try { + target.accept(this); + } + catch (NoFieldException nfe) { + if (nfe.getExpr() != target) + throw nfe; + + // it should be a static method. + exprType = CLASS; + arrayDim = 0; + className = nfe.getField(); // JVM-internal + resolver.recordPackage(className); + isStatic = true; + } + + if (arrayDim > 0) + targetClass = resolver.lookupClass(javaLangObject, true); + else if (exprType == CLASS /* && arrayDim == 0 */) + targetClass = resolver.lookupClassByJvmName(className); + else + badMethod(); + } + else + badMethod(); + } + else + fatal(); + + atMethodCallCore(targetClass, mname, args, isStatic, isSpecial, + aload0pos, cached); + } + + private static void badMethod() throws CompileError { + throw new CompileError("bad method"); + } + + /* + * atMethodCallCore() is also called by doit() in NewExpr.ProceedForNew + * + * @param targetClass the class at which method lookup starts. + * @param found not null if the method look has been already done. + */ + public void atMethodCallCore(CtClass targetClass, String mname, + ASTList args, boolean isStatic, boolean isSpecial, + int aload0pos, MemberResolver.Method found) + throws CompileError + { + int nargs = getMethodArgsLength(args); + int[] types = new int[nargs]; + int[] dims = new int[nargs]; + String[] cnames = new String[nargs]; + + if (!isStatic && found != null && found.isStatic()) { + bytecode.addOpcode(POP); + isStatic = true; + } + + int stack = bytecode.getStackDepth(); + + // generate code for evaluating arguments. + atMethodArgs(args, types, dims, cnames); + + // used by invokeinterface + int count = bytecode.getStackDepth() - stack + 1; + + if (found == null) + found = resolver.lookupMethod(targetClass, thisClass, thisMethod, + mname, types, dims, cnames); + + if (found == null) { + String msg; + if (mname.equals(MethodInfo.nameInit)) + msg = "constructor not found"; + else + msg = "Method " + mname + " not found in " + + targetClass.getName(); + + throw new CompileError(msg); + } + + atMethodCallCore2(targetClass, mname, isStatic, isSpecial, + aload0pos, count, found); + } + + private void atMethodCallCore2(CtClass targetClass, String mname, + boolean isStatic, boolean isSpecial, + int aload0pos, int count, + MemberResolver.Method found) + throws CompileError + { + CtClass declClass = found.declaring; + MethodInfo minfo = found.info; + String desc = minfo.getDescriptor(); + int acc = minfo.getAccessFlags(); + + if (mname.equals(MethodInfo.nameInit)) { + isSpecial = true; + if (declClass != targetClass) + throw new CompileError("no such constructor"); + + if (declClass != thisClass && AccessFlag.isPrivate(acc)) { + desc = getAccessibleConstructor(desc, declClass, minfo); + bytecode.addOpcode(Opcode.ACONST_NULL); // the last parameter + } + } + else if (AccessFlag.isPrivate(acc)) + if (declClass == thisClass) + isSpecial = true; + else { + isSpecial = false; + isStatic = true; + String origDesc = desc; + if ((acc & AccessFlag.STATIC) == 0) + desc = Descriptor.insertParameter(declClass.getName(), + origDesc); + + acc = AccessFlag.setPackage(acc) | AccessFlag.STATIC; + mname = getAccessiblePrivate(mname, origDesc, desc, + minfo, declClass); + } + + boolean popTarget = false; + if ((acc & AccessFlag.STATIC) != 0) { + if (!isStatic) { + /* this method is static but the target object is + on stack. It must be popped out. If aload0pos >= 0, + then the target object was pushed by aload_0. It is + overwritten by NOP. + */ + isStatic = true; + if (aload0pos >= 0) + bytecode.write(aload0pos, NOP); + else + popTarget = true; + } + + bytecode.addInvokestatic(declClass, mname, desc); + } + else if (isSpecial) // if (isSpecial && notStatic(acc)) + bytecode.addInvokespecial(declClass, mname, desc); + else { + if (!Modifier.isPublic(declClass.getModifiers()) + || declClass.isInterface() != targetClass.isInterface()) + declClass = targetClass; + + if (declClass.isInterface()) + bytecode.addInvokeinterface(declClass, mname, desc, count); + else + if (isStatic) + throw new CompileError(mname + " is not static"); + else + bytecode.addInvokevirtual(declClass, mname, desc); + } + + setReturnType(desc, isStatic, popTarget); + } + + /* + * Finds (or adds if necessary) a hidden accessor if the method + * is in an enclosing class. + * + * @param desc the descriptor of the method. + * @param declClass the class declaring the method. + */ + protected String getAccessiblePrivate(String methodName, String desc, + String newDesc, MethodInfo minfo, + CtClass declClass) + throws CompileError + { + if (isEnclosing(declClass, thisClass)) { + AccessorMaker maker = declClass.getAccessorMaker(); + if (maker != null) + return maker.getMethodAccessor(methodName, desc, newDesc, + minfo); + } + + throw new CompileError("Method " + methodName + + " is private"); + } + + /* + * Finds (or adds if necessary) a hidden constructor if the given + * constructor is in an enclosing class. + * + * @param desc the descriptor of the constructor. + * @param declClass the class declaring the constructor. + * @param minfo the method info of the constructor. + * @return the descriptor of the hidden constructor. + */ + protected String getAccessibleConstructor(String desc, CtClass declClass, + MethodInfo minfo) + throws CompileError + { + if (isEnclosing(declClass, thisClass)) { + AccessorMaker maker = declClass.getAccessorMaker(); + if (maker != null) + return maker.getConstructor(declClass, desc, minfo); + } + + throw new CompileError("the called constructor is private in " + + declClass.getName()); + } + + private boolean isEnclosing(CtClass outer, CtClass inner) { + try { + while (inner != null) { + inner = inner.getDeclaringClass(); + if (inner == outer) + return true; + } + } + catch (NotFoundException e) {} + return false; + } + + public int getMethodArgsLength(ASTList args) { + return ASTList.length(args); + } + + public void atMethodArgs(ASTList args, int[] types, int[] dims, + String[] cnames) throws CompileError { + int i = 0; + while (args != null) { + ASTree a = args.head(); + a.accept(this); + types[i] = exprType; + dims[i] = arrayDim; + cnames[i] = className; + ++i; + args = args.tail(); + } + } + + void setReturnType(String desc, boolean isStatic, boolean popTarget) + throws CompileError + { + int i = desc.indexOf(')'); + if (i < 0) + badMethod(); + + char c = desc.charAt(++i); + int dim = 0; + while (c == '[') { + ++dim; + c = desc.charAt(++i); + } + + arrayDim = dim; + if (c == 'L') { + int j = desc.indexOf(';', i + 1); + if (j < 0) + badMethod(); + + exprType = CLASS; + className = desc.substring(i + 1, j); + } + else { + exprType = MemberResolver.descToType(c); + className = null; + } + + int etype = exprType; + if (isStatic) { + if (popTarget) { + if (is2word(etype, dim)) { + bytecode.addOpcode(DUP2_X1); + bytecode.addOpcode(POP2); + bytecode.addOpcode(POP); + } + else if (etype == VOID) + bytecode.addOpcode(POP); + else { + bytecode.addOpcode(SWAP); + bytecode.addOpcode(POP); + } + } + } + } + + protected void atFieldAssign(Expr expr, int op, ASTree left, + ASTree right, boolean doDup) throws CompileError + { + CtField f = fieldAccess(left, false); + boolean is_static = resultStatic; + if (op != '=' && !is_static) + bytecode.addOpcode(DUP); + + int fi; + if (op == '=') { + FieldInfo finfo = f.getFieldInfo2(); + setFieldType(finfo); + AccessorMaker maker = isAccessibleField(f, finfo); + if (maker == null) + fi = addFieldrefInfo(f, finfo); + else + fi = 0; + } + else + fi = atFieldRead(f, is_static); + + int fType = exprType; + int fDim = arrayDim; + String cname = className; + + atAssignCore(expr, op, right, fType, fDim, cname); + + boolean is2w = is2word(fType, fDim); + if (doDup) { + int dup_code; + if (is_static) + dup_code = (is2w ? DUP2 : DUP); + else + dup_code = (is2w ? DUP2_X1 : DUP_X1); + + bytecode.addOpcode(dup_code); + } + + atFieldAssignCore(f, is_static, fi, is2w); + + exprType = fType; + arrayDim = fDim; + className = cname; + } + + /* If fi == 0, the field must be a private field in an enclosing class. + */ + private void atFieldAssignCore(CtField f, boolean is_static, int fi, + boolean is2byte) throws CompileError { + if (fi != 0) { + if (is_static) { + bytecode.add(PUTSTATIC); + bytecode.growStack(is2byte ? -2 : -1); + } + else { + bytecode.add(PUTFIELD); + bytecode.growStack(is2byte ? -3 : -2); + } + + bytecode.addIndex(fi); + } + else { + CtClass declClass = f.getDeclaringClass(); + AccessorMaker maker = declClass.getAccessorMaker(); + // make should be non null. + FieldInfo finfo = f.getFieldInfo2(); + MethodInfo minfo = maker.getFieldSetter(finfo, is_static); + bytecode.addInvokestatic(declClass, minfo.getName(), + minfo.getDescriptor()); + } + } + + /* overwritten in JvstCodeGen. + */ + public void atMember(Member mem) throws CompileError { + atFieldRead(mem); + } + + protected void atFieldRead(ASTree expr) throws CompileError + { + CtField f = fieldAccess(expr, true); + if (f == null) { + atArrayLength(expr); + return; + } + + boolean is_static = resultStatic; + ASTree cexpr = TypeChecker.getConstantFieldValue(f); + if (cexpr == null) + atFieldRead(f, is_static); + else { + cexpr.accept(this); + setFieldType(f.getFieldInfo2()); + } + } + + private void atArrayLength(ASTree expr) throws CompileError { + if (arrayDim == 0) + throw new CompileError(".length applied to a non array"); + + bytecode.addOpcode(ARRAYLENGTH); + exprType = INT; + arrayDim = 0; + } + + /** + * Generates bytecode for reading a field value. + * It returns a fieldref_info index or zero if the field is a private + * one declared in an enclosing class. + */ + private int atFieldRead(CtField f, boolean isStatic) throws CompileError { + FieldInfo finfo = f.getFieldInfo2(); + boolean is2byte = setFieldType(finfo); + AccessorMaker maker = isAccessibleField(f, finfo); + if (maker != null) { + MethodInfo minfo = maker.getFieldGetter(finfo, isStatic); + bytecode.addInvokestatic(f.getDeclaringClass(), minfo.getName(), + minfo.getDescriptor()); + return 0; + } + else { + int fi = addFieldrefInfo(f, finfo); + if (isStatic) { + bytecode.add(GETSTATIC); + bytecode.growStack(is2byte ? 2 : 1); + } + else { + bytecode.add(GETFIELD); + bytecode.growStack(is2byte ? 1 : 0); + } + + bytecode.addIndex(fi); + return fi; + } + } + + /** + * Returns null if the field is accessible. Otherwise, it throws + * an exception or it returns AccessorMaker if the field is a private + * one declared in an enclosing class. + */ + private AccessorMaker isAccessibleField(CtField f, FieldInfo finfo) + throws CompileError + { + if (AccessFlag.isPrivate(finfo.getAccessFlags()) + && f.getDeclaringClass() != thisClass) { + CtClass declClass = f.getDeclaringClass(); + if (isEnclosing(declClass, thisClass)) { + AccessorMaker maker = declClass.getAccessorMaker(); + if (maker != null) + return maker; + else + throw new CompileError("fatal error. bug?"); + } + else + throw new CompileError("Field " + f.getName() + " in " + + declClass.getName() + " is private."); + } + + return null; // accessible field + } + + /** + * Sets exprType, arrayDim, and className. + * + * @return true if the field type is long or double. + */ + private boolean setFieldType(FieldInfo finfo) throws CompileError { + String type = finfo.getDescriptor(); + + int i = 0; + int dim = 0; + char c = type.charAt(i); + while (c == '[') { + ++dim; + c = type.charAt(++i); + } + + arrayDim = dim; + exprType = MemberResolver.descToType(c); + + if (c == 'L') + className = type.substring(i + 1, type.indexOf(';', i + 1)); + else + className = null; + + boolean is2byte = (c == 'J' || c == 'D'); + return is2byte; + } + + private int addFieldrefInfo(CtField f, FieldInfo finfo) { + ConstPool cp = bytecode.getConstPool(); + String cname = f.getDeclaringClass().getName(); + int ci = cp.addClassInfo(cname); + String name = finfo.getName(); + String type = finfo.getDescriptor(); + return cp.addFieldrefInfo(ci, name, type); + } + + protected void atClassObject2(String cname) throws CompileError { + if (getMajorVersion() < ClassFile.JAVA_5) + super.atClassObject2(cname); + else + bytecode.addLdc(bytecode.getConstPool().addClassInfo(cname)); + } + + protected void atFieldPlusPlus(int token, boolean isPost, + ASTree oprand, Expr expr, boolean doDup) + throws CompileError + { + CtField f = fieldAccess(oprand, false); + boolean is_static = resultStatic; + if (!is_static) + bytecode.addOpcode(DUP); + + int fi = atFieldRead(f, is_static); + int t = exprType; + boolean is2w = is2word(t, arrayDim); + + int dup_code; + if (is_static) + dup_code = (is2w ? DUP2 : DUP); + else + dup_code = (is2w ? DUP2_X1 : DUP_X1); + + atPlusPlusCore(dup_code, doDup, token, isPost, expr); + atFieldAssignCore(f, is_static, fi, is2w); + } + + /* This method also returns a value in resultStatic. + * + * @param acceptLength true if array length is acceptable + */ + protected CtField fieldAccess(ASTree expr, boolean acceptLength) + throws CompileError + { + if (expr instanceof Member) { + String name = ((Member)expr).get(); + CtField f = null; + try { + f = thisClass.getField(name); + } + catch (NotFoundException e) { + // EXPR might be part of a static member access? + throw new NoFieldException(name, expr); + } + + boolean is_static = Modifier.isStatic(f.getModifiers()); + if (!is_static) + if (inStaticMethod) + throw new CompileError( + "not available in a static method: " + name); + else + bytecode.addAload(0); // this + + resultStatic = is_static; + return f; + } + else if (expr instanceof Expr) { + Expr e = (Expr)expr; + int op = e.getOperator(); + if (op == MEMBER) { + /* static member by # (extension by Javassist) + * For example, if int.class is parsed, the resulting tree + * is (# "java.lang.Integer" "TYPE"). + */ + CtField f = resolver.lookupField(((Symbol)e.oprand1()).get(), + (Symbol)e.oprand2()); + resultStatic = true; + return f; + } + else if (op == '.') { + CtField f = null; + try { + e.oprand1().accept(this); + /* Don't call lookupFieldByJvmName2(). + * The left operand of . is not a class name but + * a normal expression. + */ + if (exprType == CLASS && arrayDim == 0) + f = resolver.lookupFieldByJvmName(className, + (Symbol)e.oprand2()); + else if (acceptLength && arrayDim > 0 + && ((Symbol)e.oprand2()).get().equals("length")) + return null; // expr is an array length. + else + badLvalue(); + + boolean is_static = Modifier.isStatic(f.getModifiers()); + if (is_static) + bytecode.addOpcode(POP); + + resultStatic = is_static; + return f; + } + catch (NoFieldException nfe) { + if (nfe.getExpr() != e.oprand1()) + throw nfe; + + /* EXPR should be a static field. + * If EXPR might be part of a qualified class name, + * lookupFieldByJvmName2() throws NoFieldException. + */ + Symbol fname = (Symbol)e.oprand2(); + String cname = nfe.getField(); + f = resolver.lookupFieldByJvmName2(cname, fname, expr); + resolver.recordPackage(cname); + resultStatic = true; + return f; + } + } + else + badLvalue(); + } + else + badLvalue(); + + resultStatic = false; + return null; // never reach + } + + private static void badLvalue() throws CompileError { + throw new CompileError("bad l-value"); + } + + public CtClass[] makeParamList(MethodDecl md) throws CompileError { + CtClass[] params; + ASTList plist = md.getParams(); + if (plist == null) + params = new CtClass[0]; + else { + int i = 0; + params = new CtClass[plist.length()]; + while (plist != null) { + params[i++] = resolver.lookupClass((Declarator)plist.head()); + plist = plist.tail(); + } + } + + return params; + } + + public CtClass[] makeThrowsList(MethodDecl md) throws CompileError { + CtClass[] clist; + ASTList list = md.getThrows(); + if (list == null) + return null; + else { + int i = 0; + clist = new CtClass[list.length()]; + while (list != null) { + clist[i++] = resolver.lookupClassByName((ASTList)list.head()); + list = list.tail(); + } + + return clist; + } + } + + /* Converts a class name into a JVM-internal representation. + * + * It may also expand a simple class name to java.lang.*. + * For example, this converts Object into java/lang/Object. + */ + protected String resolveClassName(ASTList name) throws CompileError { + return resolver.resolveClassName(name); + } + + /* Expands a simple class name to java.lang.*. + * For example, this converts Object into java/lang/Object. + */ + protected String resolveClassName(String jvmName) throws CompileError { + return resolver.resolveJvmClassName(jvmName); + } +} diff --git a/src/main/javassist/compiler/MemberResolver.java b/src/main/javassist/compiler/MemberResolver.java new file mode 100644 index 0000000..fb2aa9b --- /dev/null +++ b/src/main/javassist/compiler/MemberResolver.java @@ -0,0 +1,583 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.compiler; + +import java.util.List; +import java.util.Iterator; +import javassist.*; +import javassist.bytecode.*; +import javassist.compiler.ast.*; + +/* Code generator methods depending on javassist.* classes. + */ +public class MemberResolver implements TokenId { + private ClassPool classPool; + + public MemberResolver(ClassPool cp) { + classPool = cp; + } + + public ClassPool getClassPool() { return classPool; } + + private static void fatal() throws CompileError { + throw new CompileError("fatal"); + } + + /** + * @param jvmClassName a class name. Not a package name. + */ + public void recordPackage(String jvmClassName) { + String classname = jvmToJavaName(jvmClassName); + for (;;) { + int i = classname.lastIndexOf('.'); + if (i > 0) { + classname = classname.substring(0, i); + classPool.recordInvalidClassName(classname); + } + else + break; + } + } + + public static class Method { + public CtClass declaring; + public MethodInfo info; + public int notmatch; + + public Method(CtClass c, MethodInfo i, int n) { + declaring = c; + info = i; + notmatch = n; + } + + /** + * Returns true if the invoked method is static. + */ + public boolean isStatic() { + int acc = info.getAccessFlags(); + return (acc & AccessFlag.STATIC) != 0; + } + } + + public Method lookupMethod(CtClass clazz, CtClass currentClass, MethodInfo current, + String methodName, + int[] argTypes, int[] argDims, + String[] argClassNames) + throws CompileError + { + Method maybe = null; + // to enable the creation of a recursively called method + if (current != null && clazz == currentClass) + if (current.getName().equals(methodName)) { + int res = compareSignature(current.getDescriptor(), + argTypes, argDims, argClassNames); + if (res != NO) { + Method r = new Method(clazz, current, res); + if (res == YES) + return r; + else + maybe = r; + } + } + + Method m = lookupMethod(clazz, methodName, argTypes, argDims, + argClassNames, maybe != null); + if (m != null) + return m; + else + return maybe; + } + + private Method lookupMethod(CtClass clazz, String methodName, + int[] argTypes, int[] argDims, + String[] argClassNames, boolean onlyExact) + throws CompileError + { + Method maybe = null; + ClassFile cf = clazz.getClassFile2(); + // If the class is an array type, the class file is null. + // If so, search the super class java.lang.Object for clone() etc. + if (cf != null) { + List list = cf.getMethods(); + int n = list.size(); + for (int i = 0; i < n; ++i) { + MethodInfo minfo = (MethodInfo)list.get(i); + if (minfo.getName().equals(methodName)) { + int res = compareSignature(minfo.getDescriptor(), + argTypes, argDims, argClassNames); + if (res != NO) { + Method r = new Method(clazz, minfo, res); + if (res == YES) + return r; + else if (maybe == null || maybe.notmatch > res) + maybe = r; + } + } + } + } + + if (onlyExact) + maybe = null; + else + onlyExact = maybe != null; + + int mod = clazz.getModifiers(); + boolean isIntf = Modifier.isInterface(mod); + try { + // skip searching java.lang.Object if clazz is an interface type. + if (!isIntf) { + CtClass pclazz = clazz.getSuperclass(); + if (pclazz != null) { + Method r = lookupMethod(pclazz, methodName, argTypes, + argDims, argClassNames, onlyExact); + if (r != null) + return r; + } + } + } + catch (NotFoundException e) {} + + if (isIntf || Modifier.isAbstract(mod)) + try { + CtClass[] ifs = clazz.getInterfaces(); + int size = ifs.length; + for (int i = 0; i < size; ++i) { + Method r = lookupMethod(ifs[i], methodName, + argTypes, argDims, argClassNames, + onlyExact); + if (r != null) + return r; + } + + if (isIntf) { + // finally search java.lang.Object. + CtClass pclazz = clazz.getSuperclass(); + if (pclazz != null) { + Method r = lookupMethod(pclazz, methodName, argTypes, + argDims, argClassNames, onlyExact); + if (r != null) + return r; + } + } + } + catch (NotFoundException e) {} + + return maybe; + } + + private static final int YES = 0; + private static final int NO = -1; + + /* + * Returns YES if actual parameter types matches the given signature. + * + * argTypes, argDims, and argClassNames represent actual parameters. + * + * This method does not correctly implement the Java method dispatch + * algorithm. + * + * If some of the parameter types exactly match but others are subtypes of + * the corresponding type in the signature, this method returns the number + * of parameter types that do not exactly match. + */ + private int compareSignature(String desc, int[] argTypes, + int[] argDims, String[] argClassNames) + throws CompileError + { + int result = YES; + int i = 1; + int nArgs = argTypes.length; + if (nArgs != Descriptor.numOfParameters(desc)) + return NO; + + int len = desc.length(); + for (int n = 0; i < len; ++n) { + char c = desc.charAt(i++); + if (c == ')') + return (n == nArgs ? result : NO); + else if (n >= nArgs) + return NO; + + int dim = 0; + while (c == '[') { + ++dim; + c = desc.charAt(i++); + } + + if (argTypes[n] == NULL) { + if (dim == 0 && c != 'L') + return NO; + + if (c == 'L') + i = desc.indexOf(';', i) + 1; + } + else if (argDims[n] != dim) { + if (!(dim == 0 && c == 'L' + && desc.startsWith("java/lang/Object;", i))) + return NO; + + // if the thread reaches here, c must be 'L'. + i = desc.indexOf(';', i) + 1; + result++; + if (i <= 0) + return NO; // invalid descriptor? + } + else if (c == 'L') { // not compare + int j = desc.indexOf(';', i); + if (j < 0 || argTypes[n] != CLASS) + return NO; + + String cname = desc.substring(i, j); + if (!cname.equals(argClassNames[n])) { + CtClass clazz = lookupClassByJvmName(argClassNames[n]); + try { + if (clazz.subtypeOf(lookupClassByJvmName(cname))) + result++; + else + return NO; + } + catch (NotFoundException e) { + result++; // should be NO? + } + } + + i = j + 1; + } + else { + int t = descToType(c); + int at = argTypes[n]; + if (t != at) + if (t == INT + && (at == SHORT || at == BYTE || at == CHAR)) + result++; + else + return NO; + } + } + + return NO; + } + + /** + * Only used by fieldAccess() in MemberCodeGen and TypeChecker. + * + * @param jvmClassName a JVM class name. e.g. java/lang/String + */ + public CtField lookupFieldByJvmName2(String jvmClassName, Symbol fieldSym, + ASTree expr) throws NoFieldException + { + String field = fieldSym.get(); + CtClass cc = null; + try { + cc = lookupClass(jvmToJavaName(jvmClassName), true); + } + catch (CompileError e) { + // EXPR might be part of a qualified class name. + throw new NoFieldException(jvmClassName + "/" + field, expr); + } + + try { + return cc.getField(field); + } + catch (NotFoundException e) { + // maybe an inner class. + jvmClassName = javaToJvmName(cc.getName()); + throw new NoFieldException(jvmClassName + "$" + field, expr); + } + } + + /** + * @param jvmClassName a JVM class name. e.g. java/lang/String + */ + public CtField lookupFieldByJvmName(String jvmClassName, Symbol fieldName) + throws CompileError + { + return lookupField(jvmToJavaName(jvmClassName), fieldName); + } + + /** + * @param name a qualified class name. e.g. java.lang.String + */ + public CtField lookupField(String className, Symbol fieldName) + throws CompileError + { + CtClass cc = lookupClass(className, false); + try { + return cc.getField(fieldName.get()); + } + catch (NotFoundException e) {} + throw new CompileError("no such field: " + fieldName.get()); + } + + public CtClass lookupClassByName(ASTList name) throws CompileError { + return lookupClass(Declarator.astToClassName(name, '.'), false); + } + + public CtClass lookupClassByJvmName(String jvmName) throws CompileError { + return lookupClass(jvmToJavaName(jvmName), false); + } + + public CtClass lookupClass(Declarator decl) throws CompileError { + return lookupClass(decl.getType(), decl.getArrayDim(), + decl.getClassName()); + } + + /** + * @parma classname jvm class name. + */ + public CtClass lookupClass(int type, int dim, String classname) + throws CompileError + { + String cname = ""; + CtClass clazz; + if (type == CLASS) { + clazz = lookupClassByJvmName(classname); + if (dim > 0) + cname = clazz.getName(); + else + return clazz; + } + else + cname = getTypeName(type); + + while (dim-- > 0) + cname += "[]"; + + return lookupClass(cname, false); + } + + /* + * type cannot be CLASS + */ + static String getTypeName(int type) throws CompileError { + String cname = ""; + switch (type) { + case BOOLEAN : + cname = "boolean"; + break; + case CHAR : + cname = "char"; + break; + case BYTE : + cname = "byte"; + break; + case SHORT : + cname = "short"; + break; + case INT : + cname = "int"; + break; + case LONG : + cname = "long"; + break; + case FLOAT : + cname = "float"; + break; + case DOUBLE : + cname = "double"; + break; + case VOID : + cname = "void"; + break; + default : + fatal(); + } + + return cname; + } + + /** + * @param name a qualified class name. e.g. java.lang.String + */ + public CtClass lookupClass(String name, boolean notCheckInner) + throws CompileError + { + try { + return lookupClass0(name, notCheckInner); + } + catch (NotFoundException e) { + return searchImports(name); + } + } + + private CtClass searchImports(String orgName) + throws CompileError + { + if (orgName.indexOf('.') < 0) { + Iterator it = classPool.getImportedPackages(); + while (it.hasNext()) { + String pac = (String)it.next(); + String fqName = pac + '.' + orgName; + try { + CtClass cc = classPool.get(fqName); + // if the class is found, + classPool.recordInvalidClassName(orgName); + return cc; + } + catch (NotFoundException e) { + classPool.recordInvalidClassName(fqName); + try { + if (pac.endsWith("." + orgName)) { + CtClass cc = classPool.get(pac); + // if the class is found, + classPool.recordInvalidClassName(orgName); + return cc; + } + } + catch (NotFoundException e2) { + classPool.recordInvalidClassName(pac); + } + } + } + } + + throw new CompileError("no such class: " + orgName); + } + + private CtClass lookupClass0(String classname, boolean notCheckInner) + throws NotFoundException + { + CtClass cc = null; + do { + try { + cc = classPool.get(classname); + } + catch (NotFoundException e) { + int i = classname.lastIndexOf('.'); + if (notCheckInner || i < 0) + throw e; + else { + StringBuffer sbuf = new StringBuffer(classname); + sbuf.setCharAt(i, '$'); + classname = sbuf.toString(); + } + } + } while (cc == null); + return cc; + } + + /* Converts a class name into a JVM-internal representation. + * + * It may also expand a simple class name to java.lang.*. + * For example, this converts Object into java/lang/Object. + */ + public String resolveClassName(ASTList name) throws CompileError { + if (name == null) + return null; + else + return javaToJvmName(lookupClassByName(name).getName()); + } + + /* Expands a simple class name to java.lang.*. + * For example, this converts Object into java/lang/Object. + */ + public String resolveJvmClassName(String jvmName) throws CompileError { + if (jvmName == null) + return null; + else + return javaToJvmName(lookupClassByJvmName(jvmName).getName()); + } + + public static CtClass getSuperclass(CtClass c) throws CompileError { + try { + CtClass sc = c.getSuperclass(); + if (sc != null) + return sc; + } + catch (NotFoundException e) {} + throw new CompileError("cannot find the super class of " + + c.getName()); + } + + public static String javaToJvmName(String classname) { + return classname.replace('.', '/'); + } + + public static String jvmToJavaName(String classname) { + return classname.replace('/', '.'); + } + + public static int descToType(char c) throws CompileError { + switch (c) { + case 'Z' : + return BOOLEAN; + case 'C' : + return CHAR; + case 'B' : + return BYTE; + case 'S' : + return SHORT; + case 'I' : + return INT; + case 'J' : + return LONG; + case 'F' : + return FLOAT; + case 'D' : + return DOUBLE; + case 'V' : + return VOID; + case 'L' : + case '[' : + return CLASS; + default : + fatal(); + return VOID; // never reach here + } + } + + public static int getModifiers(ASTList mods) { + int m = 0; + while (mods != null) { + Keyword k = (Keyword)mods.head(); + mods = mods.tail(); + switch (k.get()) { + case STATIC : + m |= Modifier.STATIC; + break; + case FINAL : + m |= Modifier.FINAL; + break; + case SYNCHRONIZED : + m |= Modifier.SYNCHRONIZED; + break; + case ABSTRACT : + m |= Modifier.ABSTRACT; + break; + case PUBLIC : + m |= Modifier.PUBLIC; + break; + case PROTECTED : + m |= Modifier.PROTECTED; + break; + case PRIVATE : + m |= Modifier.PRIVATE; + break; + case VOLATILE : + m |= Modifier.VOLATILE; + break; + case TRANSIENT : + m |= Modifier.TRANSIENT; + break; + case STRICT : + m |= Modifier.STRICT; + break; + } + } + + return m; + } +} diff --git a/src/main/javassist/compiler/NoFieldException.java b/src/main/javassist/compiler/NoFieldException.java new file mode 100644 index 0000000..f6e114b --- /dev/null +++ b/src/main/javassist/compiler/NoFieldException.java @@ -0,0 +1,39 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.compiler; + +import javassist.compiler.ast.ASTree; + +public class NoFieldException extends CompileError { + private String fieldName; + private ASTree expr; + + /* NAME must be JVM-internal representation. + */ + public NoFieldException(String name, ASTree e) { + super("no such field: " + name); + fieldName = name; + expr = e; + } + + /* The returned name should be JVM-internal representation. + */ + public String getField() { return fieldName; } + + /* Returns the expression where this exception is thrown. + */ + public ASTree getExpr() { return expr; } +} diff --git a/src/main/javassist/compiler/Parser.java b/src/main/javassist/compiler/Parser.java new file mode 100644 index 0000000..d483814 --- /dev/null +++ b/src/main/javassist/compiler/Parser.java @@ -0,0 +1,1342 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.compiler; + +import javassist.compiler.ast.*; + +public final class Parser implements TokenId { + private Lex lex; + + public Parser(Lex lex) { + this.lex = lex; + } + + public boolean hasMore() { return lex.lookAhead() >= 0; } + + /* member.declaration + * : method.declaration | field.declaration + */ + public ASTList parseMember(SymbolTable tbl) throws CompileError { + ASTList mem = parseMember1(tbl); + if (mem instanceof MethodDecl) + return parseMethod2(tbl, (MethodDecl)mem); + else + return mem; + } + + /* A method body is not parsed. + */ + public ASTList parseMember1(SymbolTable tbl) throws CompileError { + ASTList mods = parseMemberMods(); + Declarator d; + boolean isConstructor = false; + if (lex.lookAhead() == Identifier && lex.lookAhead(1) == '(') { + d = new Declarator(VOID, 0); + isConstructor = true; + } + else + d = parseFormalType(tbl); + + if (lex.get() != Identifier) + throw new SyntaxError(lex); + + String name; + if (isConstructor) + name = MethodDecl.initName; + else + name = lex.getString(); + + d.setVariable(new Symbol(name)); + if (isConstructor || lex.lookAhead() == '(') + return parseMethod1(tbl, isConstructor, mods, d); + else + return parseField(tbl, mods, d); + } + + /* field.declaration + * : member.modifiers + * formal.type Identifier + * [ "=" expression ] ";" + */ + private FieldDecl parseField(SymbolTable tbl, ASTList mods, + Declarator d) throws CompileError + { + ASTree expr = null; + if (lex.lookAhead() == '=') { + lex.get(); + expr = parseExpression(tbl); + } + + int c = lex.get(); + if (c == ';') + return new FieldDecl(mods, new ASTList(d, new ASTList(expr))); + else if (c == ',') + throw new CompileError( + "only one field can be declared in one declaration", lex); + else + throw new SyntaxError(lex); + } + + /* method.declaration + * : member.modifiers + * [ formal.type ] + * Identifier "(" [ formal.parameter ( "," formal.parameter )* ] ")" + * array.dimension + * [ THROWS class.type ( "," class.type ) ] + * ( block.statement | ";" ) + * + * Note that a method body is not parsed. + */ + private MethodDecl parseMethod1(SymbolTable tbl, boolean isConstructor, + ASTList mods, Declarator d) + throws CompileError + { + if (lex.get() != '(') + throw new SyntaxError(lex); + + ASTList parms = null; + if (lex.lookAhead() != ')') + while (true) { + parms = ASTList.append(parms, parseFormalParam(tbl)); + int t = lex.lookAhead(); + if (t == ',') + lex.get(); + else if (t == ')') + break; + } + + lex.get(); // ')' + d.addArrayDim(parseArrayDimension()); + if (isConstructor && d.getArrayDim() > 0) + throw new SyntaxError(lex); + + ASTList throwsList = null; + if (lex.lookAhead() == THROWS) { + lex.get(); + while (true) { + throwsList = ASTList.append(throwsList, parseClassType(tbl)); + if (lex.lookAhead() == ',') + lex.get(); + else + break; + } + } + + return new MethodDecl(mods, new ASTList(d, + ASTList.make(parms, throwsList, null))); + } + + /* Parses a method body. + */ + public MethodDecl parseMethod2(SymbolTable tbl, MethodDecl md) + throws CompileError + { + Stmnt body = null; + if (lex.lookAhead() == ';') + lex.get(); + else { + body = parseBlock(tbl); + if (body == null) + body = new Stmnt(BLOCK); + } + + md.sublist(4).setHead(body); + return md; + } + + /* member.modifiers + * : ( FINAL | SYNCHRONIZED | ABSTRACT + * | PUBLIC | PROTECTED | PRIVATE | STATIC + * | VOLATILE | TRANSIENT | STRICT )* + */ + private ASTList parseMemberMods() { + int t; + ASTList list = null; + while (true) { + t = lex.lookAhead(); + if (t == ABSTRACT || t == FINAL || t == PUBLIC || t == PROTECTED + || t == PRIVATE || t == SYNCHRONIZED || t == STATIC + || t == VOLATILE || t == TRANSIENT || t == STRICT) + list = new ASTList(new Keyword(lex.get()), list); + else + break; + } + + return list; + } + + /* formal.type : ( build-in-type | class.type ) array.dimension + */ + private Declarator parseFormalType(SymbolTable tbl) throws CompileError { + int t = lex.lookAhead(); + if (isBuiltinType(t) || t == VOID) { + lex.get(); // primitive type + int dim = parseArrayDimension(); + return new Declarator(t, dim); + } + else { + ASTList name = parseClassType(tbl); + int dim = parseArrayDimension(); + return new Declarator(name, dim); + } + } + + private static boolean isBuiltinType(int t) { + return (t == BOOLEAN || t == BYTE || t == CHAR || t == SHORT + || t == INT || t == LONG || t == FLOAT || t == DOUBLE); + } + + /* formal.parameter : formal.type Identifier array.dimension + */ + private Declarator parseFormalParam(SymbolTable tbl) + throws CompileError + { + Declarator d = parseFormalType(tbl); + if (lex.get() != Identifier) + throw new SyntaxError(lex); + + String name = lex.getString(); + d.setVariable(new Symbol(name)); + d.addArrayDim(parseArrayDimension()); + tbl.append(name, d); + return d; + } + + /* statement : [ label ":" ]* labeled.statement + * + * labeled.statement + * : block.statement + * | if.statement + * | while.statement + * | do.statement + * | for.statement + * | switch.statement + * | try.statement + * | return.statement + * | thorw.statement + * | break.statement + * | continue.statement + * | declaration.or.expression + * | ";" + * + * This method may return null (empty statement). + */ + public Stmnt parseStatement(SymbolTable tbl) + throws CompileError + { + int t = lex.lookAhead(); + if (t == '{') + return parseBlock(tbl); + else if (t == ';') { + lex.get(); + return new Stmnt(BLOCK); // empty statement + } + else if (t == Identifier && lex.lookAhead(1) == ':') { + lex.get(); // Identifier + String label = lex.getString(); + lex.get(); // ':' + return Stmnt.make(LABEL, new Symbol(label), parseStatement(tbl)); + } + else if (t == IF) + return parseIf(tbl); + else if (t == WHILE) + return parseWhile(tbl); + else if (t == DO) + return parseDo(tbl); + else if (t == FOR) + return parseFor(tbl); + else if (t == TRY) + return parseTry(tbl); + else if (t == SWITCH) + return parseSwitch(tbl); + else if (t == SYNCHRONIZED) + return parseSynchronized(tbl); + else if (t == RETURN) + return parseReturn(tbl); + else if (t == THROW) + return parseThrow(tbl); + else if (t == BREAK) + return parseBreak(tbl); + else if (t == CONTINUE) + return parseContinue(tbl); + else + return parseDeclarationOrExpression(tbl, false); + } + + /* block.statement : "{" statement* "}" + */ + private Stmnt parseBlock(SymbolTable tbl) throws CompileError { + if (lex.get() != '{') + throw new SyntaxError(lex); + + Stmnt body = null; + SymbolTable tbl2 = new SymbolTable(tbl); + while (lex.lookAhead() != '}') { + Stmnt s = parseStatement(tbl2); + if (s != null) + body = (Stmnt)ASTList.concat(body, new Stmnt(BLOCK, s)); + } + + lex.get(); // '}' + if (body == null) + return new Stmnt(BLOCK); // empty block + else + return body; + } + + /* if.statement : IF "(" expression ")" statement + * [ ELSE statement ] + */ + private Stmnt parseIf(SymbolTable tbl) throws CompileError { + int t = lex.get(); // IF + ASTree expr = parseParExpression(tbl); + Stmnt thenp = parseStatement(tbl); + Stmnt elsep; + if (lex.lookAhead() == ELSE) { + lex.get(); + elsep = parseStatement(tbl); + } + else + elsep = null; + + return new Stmnt(t, expr, new ASTList(thenp, new ASTList(elsep))); + } + + /* while.statement : WHILE "(" expression ")" statement + */ + private Stmnt parseWhile(SymbolTable tbl) + throws CompileError + { + int t = lex.get(); // WHILE + ASTree expr = parseParExpression(tbl); + Stmnt body = parseStatement(tbl); + return new Stmnt(t, expr, body); + } + + /* do.statement : DO statement WHILE "(" expression ")" ";" + */ + private Stmnt parseDo(SymbolTable tbl) throws CompileError { + int t = lex.get(); // DO + Stmnt body = parseStatement(tbl); + if (lex.get() != WHILE || lex.get() != '(') + throw new SyntaxError(lex); + + ASTree expr = parseExpression(tbl); + if (lex.get() != ')' || lex.get() != ';') + throw new SyntaxError(lex); + + return new Stmnt(t, expr, body); + } + + /* for.statement : FOR "(" decl.or.expr expression ";" expression ")" + * statement + */ + private Stmnt parseFor(SymbolTable tbl) throws CompileError { + Stmnt expr1, expr3; + ASTree expr2; + int t = lex.get(); // FOR + + SymbolTable tbl2 = new SymbolTable(tbl); + + if (lex.get() != '(') + throw new SyntaxError(lex); + + if (lex.lookAhead() == ';') { + lex.get(); + expr1 = null; + } + else + expr1 = parseDeclarationOrExpression(tbl2, true); + + if (lex.lookAhead() == ';') + expr2 = null; + else + expr2 = parseExpression(tbl2); + + if (lex.get() != ';') + throw new CompileError("; is missing", lex); + + if (lex.lookAhead() == ')') + expr3 = null; + else + expr3 = parseExprList(tbl2); + + if (lex.get() != ')') + throw new CompileError(") is missing", lex); + + Stmnt body = parseStatement(tbl2); + return new Stmnt(t, expr1, new ASTList(expr2, + new ASTList(expr3, body))); + } + + /* switch.statement : SWITCH "(" expression ")" "{" switch.block "}" + * + * swtich.block : ( switch.label statement* )* + * + * swtich.label : DEFAULT ":" + * | CASE const.expression ":" + */ + private Stmnt parseSwitch(SymbolTable tbl) throws CompileError { + int t = lex.get(); // SWITCH + ASTree expr = parseParExpression(tbl); + Stmnt body = parseSwitchBlock(tbl); + return new Stmnt(t, expr, body); + } + + private Stmnt parseSwitchBlock(SymbolTable tbl) throws CompileError { + if (lex.get() != '{') + throw new SyntaxError(lex); + + SymbolTable tbl2 = new SymbolTable(tbl); + Stmnt s = parseStmntOrCase(tbl2); + if (s == null) + throw new CompileError("empty switch block", lex); + + int op = s.getOperator(); + if (op != CASE && op != DEFAULT) + throw new CompileError("no case or default in a switch block", + lex); + + Stmnt body = new Stmnt(BLOCK, s); + while (lex.lookAhead() != '}') { + Stmnt s2 = parseStmntOrCase(tbl2); + if (s2 != null) { + int op2 = s2.getOperator(); + if (op2 == CASE || op2 == DEFAULT) { + body = (Stmnt)ASTList.concat(body, new Stmnt(BLOCK, s2)); + s = s2; + } + else + s = (Stmnt)ASTList.concat(s, new Stmnt(BLOCK, s2)); + } + } + + lex.get(); // '}' + return body; + } + + private Stmnt parseStmntOrCase(SymbolTable tbl) throws CompileError { + int t = lex.lookAhead(); + if (t != CASE && t != DEFAULT) + return parseStatement(tbl); + + lex.get(); + Stmnt s; + if (t == CASE) + s = new Stmnt(t, parseExpression(tbl)); + else + s = new Stmnt(DEFAULT); + + if (lex.get() != ':') + throw new CompileError(": is missing", lex); + + return s; + } + + /* synchronized.statement : + * SYNCHRONIZED "(" expression ")" block.statement + */ + private Stmnt parseSynchronized(SymbolTable tbl) throws CompileError { + int t = lex.get(); // SYNCHRONIZED + if (lex.get() != '(') + throw new SyntaxError(lex); + + ASTree expr = parseExpression(tbl); + if (lex.get() != ')') + throw new SyntaxError(lex); + + Stmnt body = parseBlock(tbl); + return new Stmnt(t, expr, body); + } + + /* try.statement + * : TRY block.statement + * [ CATCH "(" class.type Identifier ")" block.statement ]* + * [ FINALLY block.statement ]* + */ + private Stmnt parseTry(SymbolTable tbl) throws CompileError { + lex.get(); // TRY + Stmnt block = parseBlock(tbl); + ASTList catchList = null; + while (lex.lookAhead() == CATCH) { + lex.get(); // CATCH + if (lex.get() != '(') + throw new SyntaxError(lex); + + SymbolTable tbl2 = new SymbolTable(tbl); + Declarator d = parseFormalParam(tbl2); + if (d.getArrayDim() > 0 || d.getType() != CLASS) + throw new SyntaxError(lex); + + if (lex.get() != ')') + throw new SyntaxError(lex); + + Stmnt b = parseBlock(tbl2); + catchList = ASTList.append(catchList, new Pair(d, b)); + } + + Stmnt finallyBlock = null; + if (lex.lookAhead() == FINALLY) { + lex.get(); // FINALLY + finallyBlock = parseBlock(tbl); + } + + return Stmnt.make(TRY, block, catchList, finallyBlock); + } + + /* return.statement : RETURN [ expression ] ";" + */ + private Stmnt parseReturn(SymbolTable tbl) throws CompileError { + int t = lex.get(); // RETURN + Stmnt s = new Stmnt(t); + if (lex.lookAhead() != ';') + s.setLeft(parseExpression(tbl)); + + if (lex.get() != ';') + throw new CompileError("; is missing", lex); + + return s; + } + + /* throw.statement : THROW expression ";" + */ + private Stmnt parseThrow(SymbolTable tbl) throws CompileError { + int t = lex.get(); // THROW + ASTree expr = parseExpression(tbl); + if (lex.get() != ';') + throw new CompileError("; is missing", lex); + + return new Stmnt(t, expr); + } + + /* break.statement : BREAK [ Identifier ] ";" + */ + private Stmnt parseBreak(SymbolTable tbl) + throws CompileError + { + return parseContinue(tbl); + } + + /* continue.statement : CONTINUE [ Identifier ] ";" + */ + private Stmnt parseContinue(SymbolTable tbl) + throws CompileError + { + int t = lex.get(); // CONTINUE + Stmnt s = new Stmnt(t); + int t2 = lex.get(); + if (t2 == Identifier) { + s.setLeft(new Symbol(lex.getString())); + t2 = lex.get(); + } + + if (t2 != ';') + throw new CompileError("; is missing", lex); + + return s; + } + + /* declaration.or.expression + * : [ FINAL ] built-in-type array.dimension declarators + * | [ FINAL ] class.type array.dimension declarators + * | expression ';' + * | expr.list ';' if exprList is true + * + * Note: FINAL is currently ignored. This must be fixed + * in future. + */ + private Stmnt parseDeclarationOrExpression(SymbolTable tbl, + boolean exprList) + throws CompileError + { + int t = lex.lookAhead(); + while (t == FINAL) { + lex.get(); + t = lex.lookAhead(); + } + + if (isBuiltinType(t)) { + t = lex.get(); + int dim = parseArrayDimension(); + return parseDeclarators(tbl, new Declarator(t, dim)); + } + else if (t == Identifier) { + int i = nextIsClassType(0); + if (i >= 0) + if (lex.lookAhead(i) == Identifier) { + ASTList name = parseClassType(tbl); + int dim = parseArrayDimension(); + return parseDeclarators(tbl, new Declarator(name, dim)); + } + } + + Stmnt expr; + if (exprList) + expr = parseExprList(tbl); + else + expr = new Stmnt(EXPR, parseExpression(tbl)); + + if (lex.get() != ';') + throw new CompileError("; is missing", lex); + + return expr; + } + + /* expr.list : ( expression ',')* expression + */ + private Stmnt parseExprList(SymbolTable tbl) throws CompileError { + Stmnt expr = null; + for (;;) { + Stmnt e = new Stmnt(EXPR, parseExpression(tbl)); + expr = (Stmnt)ASTList.concat(expr, new Stmnt(BLOCK, e)); + if (lex.lookAhead() == ',') + lex.get(); + else + return expr; + } + } + + /* declarators : declarator [ ',' declarator ]* ';' + */ + private Stmnt parseDeclarators(SymbolTable tbl, Declarator d) + throws CompileError + { + Stmnt decl = null; + for (;;) { + decl = (Stmnt)ASTList.concat(decl, + new Stmnt(DECL, parseDeclarator(tbl, d))); + int t = lex.get(); + if (t == ';') + return decl; + else if (t != ',') + throw new CompileError("; is missing", lex); + } + } + + /* declarator : Identifier array.dimension [ '=' initializer ] + */ + private Declarator parseDeclarator(SymbolTable tbl, Declarator d) + throws CompileError + { + if (lex.get() != Identifier || d.getType() == VOID) + throw new SyntaxError(lex); + + String name = lex.getString(); + Symbol symbol = new Symbol(name); + int dim = parseArrayDimension(); + ASTree init = null; + if (lex.lookAhead() == '=') { + lex.get(); + init = parseInitializer(tbl); + } + + Declarator decl = d.make(symbol, dim, init); + tbl.append(name, decl); + return decl; + } + + /* initializer : expression | array.initializer + */ + private ASTree parseInitializer(SymbolTable tbl) throws CompileError { + if (lex.lookAhead() == '{') + return parseArrayInitializer(tbl); + else + return parseExpression(tbl); + } + + /* array.initializer : + * '{' (( array.initializer | expression ) ',')* '}' + */ + private ArrayInit parseArrayInitializer(SymbolTable tbl) + throws CompileError + { + lex.get(); // '{' + ASTree expr = parseExpression(tbl); + ArrayInit init = new ArrayInit(expr); + while (lex.lookAhead() == ',') { + lex.get(); + expr = parseExpression(tbl); + ASTList.append(init, expr); + } + + if (lex.get() != '}') + throw new SyntaxError(lex); + + return init; + } + + /* par.expression : '(' expression ')' + */ + private ASTree parseParExpression(SymbolTable tbl) throws CompileError { + if (lex.get() != '(') + throw new SyntaxError(lex); + + ASTree expr = parseExpression(tbl); + if (lex.get() != ')') + throw new SyntaxError(lex); + + return expr; + } + + /* expression : conditional.expr + * | conditional.expr assign.op expression (right-to-left) + */ + public ASTree parseExpression(SymbolTable tbl) throws CompileError { + ASTree left = parseConditionalExpr(tbl); + if (!isAssignOp(lex.lookAhead())) + return left; + + int t = lex.get(); + ASTree right = parseExpression(tbl); + return AssignExpr.makeAssign(t, left, right); + } + + private static boolean isAssignOp(int t) { + return t == '=' || t == MOD_E || t == AND_E + || t == MUL_E || t == PLUS_E || t == MINUS_E || t == DIV_E + || t == EXOR_E || t == OR_E || t == LSHIFT_E + || t == RSHIFT_E || t == ARSHIFT_E; + } + + /* conditional.expr (right-to-left) + * : logical.or.expr [ '?' expression ':' conditional.expr ] + */ + private ASTree parseConditionalExpr(SymbolTable tbl) throws CompileError { + ASTree cond = parseBinaryExpr(tbl); + if (lex.lookAhead() == '?') { + lex.get(); + ASTree thenExpr = parseExpression(tbl); + if (lex.get() != ':') + throw new CompileError(": is missing", lex); + + ASTree elseExpr = parseExpression(tbl); + return new CondExpr(cond, thenExpr, elseExpr); + } + else + return cond; + } + + /* logical.or.expr 10 (operator precedence) + * : logical.and.expr + * | logical.or.expr OROR logical.and.expr left-to-right + * + * logical.and.expr 9 + * : inclusive.or.expr + * | logical.and.expr ANDAND inclusive.or.expr + * + * inclusive.or.expr 8 + * : exclusive.or.expr + * | inclusive.or.expr "|" exclusive.or.expr + * + * exclusive.or.expr 7 + * : and.expr + * | exclusive.or.expr "^" and.expr + * + * and.expr 6 + * : equality.expr + * | and.expr "&" equality.expr + * + * equality.expr 5 + * : relational.expr + * | equality.expr (EQ | NEQ) relational.expr + * + * relational.expr 4 + * : shift.expr + * | relational.expr (LE | GE | "<" | ">") shift.expr + * | relational.expr INSTANCEOF class.type ("[" "]")* + * + * shift.expr 3 + * : additive.expr + * | shift.expr (LSHIFT | RSHIFT | ARSHIFT) additive.expr + * + * additive.expr 2 + * : multiply.expr + * | additive.expr ("+" | "-") multiply.expr + * + * multiply.expr 1 + * : unary.expr + * | multiply.expr ("*" | "/" | "%") unary.expr + */ + private ASTree parseBinaryExpr(SymbolTable tbl) throws CompileError { + ASTree expr = parseUnaryExpr(tbl); + for (;;) { + int t = lex.lookAhead(); + int p = getOpPrecedence(t); + if (p == 0) + return expr; + else + expr = binaryExpr2(tbl, expr, p); + } + } + + private ASTree parseInstanceOf(SymbolTable tbl, ASTree expr) + throws CompileError + { + int t = lex.lookAhead(); + if (isBuiltinType(t)) { + lex.get(); // primitive type + int dim = parseArrayDimension(); + return new InstanceOfExpr(t, dim, expr); + } + else { + ASTList name = parseClassType(tbl); + int dim = parseArrayDimension(); + return new InstanceOfExpr(name, dim, expr); + } + } + + private ASTree binaryExpr2(SymbolTable tbl, ASTree expr, int prec) + throws CompileError + { + int t = lex.get(); + if (t == INSTANCEOF) + return parseInstanceOf(tbl, expr); + + ASTree expr2 = parseUnaryExpr(tbl); + for (;;) { + int t2 = lex.lookAhead(); + int p2 = getOpPrecedence(t2); + if (p2 != 0 && prec > p2) + expr2 = binaryExpr2(tbl, expr2, p2); + else + return BinExpr.makeBin(t, expr, expr2); + } + } + + // !"#$%&'( )*+,-./0 12345678 9:;<=>? + private static final int[] binaryOpPrecedence + = { 0, 0, 0, 0, 1, 6, 0, 0, + 0, 1, 2, 0, 2, 0, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 4, 0, 4, 0 }; + + private int getOpPrecedence(int c) { + if ('!' <= c && c <= '?') + return binaryOpPrecedence[c - '!']; + else if (c == '^') + return 7; + else if (c == '|') + return 8; + else if (c == ANDAND) + return 9; + else if (c == OROR) + return 10; + else if (c == EQ || c == NEQ) + return 5; + else if (c == LE || c == GE || c == INSTANCEOF) + return 4; + else if (c == LSHIFT || c == RSHIFT || c == ARSHIFT) + return 3; + else + return 0; // not a binary operator + } + + /* unary.expr : "++"|"--" unary.expr + | "+"|"-" unary.expr + | "!"|"~" unary.expr + | cast.expr + | postfix.expr + + unary.expr.not.plus.minus is a unary expression starting without + "+", "-", "++", or "--". + */ + private ASTree parseUnaryExpr(SymbolTable tbl) throws CompileError { + int t; + switch (lex.lookAhead()) { + case '+' : + case '-' : + case PLUSPLUS : + case MINUSMINUS : + case '!' : + case '~' : + t = lex.get(); + if (t == '-') { + int t2 = lex.lookAhead(); + switch (t2) { + case LongConstant : + case IntConstant : + case CharConstant : + lex.get(); + return new IntConst(-lex.getLong(), t2); + case DoubleConstant : + case FloatConstant : + lex.get(); + return new DoubleConst(-lex.getDouble(), t2); + default : + break; + } + } + + return Expr.make(t, parseUnaryExpr(tbl)); + case '(' : + return parseCast(tbl); + default : + return parsePostfix(tbl); + } + } + + /* cast.expr : "(" builtin.type ("[" "]")* ")" unary.expr + | "(" class.type ("[" "]")* ")" unary.expr2 + + unary.expr2 is a unary.expr beginning with "(", NULL, StringL, + Identifier, THIS, SUPER, or NEW. + + Either "(int.class)" or "(String[].class)" is a not cast expression. + */ + private ASTree parseCast(SymbolTable tbl) throws CompileError { + int t = lex.lookAhead(1); + if (isBuiltinType(t) && nextIsBuiltinCast()) { + lex.get(); // '(' + lex.get(); // primitive type + int dim = parseArrayDimension(); + if (lex.get() != ')') + throw new CompileError(") is missing", lex); + + return new CastExpr(t, dim, parseUnaryExpr(tbl)); + } + else if (t == Identifier && nextIsClassCast()) { + lex.get(); // '(' + ASTList name = parseClassType(tbl); + int dim = parseArrayDimension(); + if (lex.get() != ')') + throw new CompileError(") is missing", lex); + + return new CastExpr(name, dim, parseUnaryExpr(tbl)); + } + else + return parsePostfix(tbl); + } + + private boolean nextIsBuiltinCast() { + int t; + int i = 2; + while ((t = lex.lookAhead(i++)) == '[') + if (lex.lookAhead(i++) != ']') + return false; + + return lex.lookAhead(i - 1) == ')'; + } + + private boolean nextIsClassCast() { + int i = nextIsClassType(1); + if (i < 0) + return false; + + int t = lex.lookAhead(i); + if (t != ')') + return false; + + t = lex.lookAhead(i + 1); + return t == '(' || t == NULL || t == StringL + || t == Identifier || t == THIS || t == SUPER || t == NEW + || t == TRUE || t == FALSE || t == LongConstant + || t == IntConstant || t == CharConstant + || t == DoubleConstant || t == FloatConstant; + } + + private int nextIsClassType(int i) { + int t; + while (lex.lookAhead(++i) == '.') + if (lex.lookAhead(++i) != Identifier) + return -1; + + while ((t = lex.lookAhead(i++)) == '[') + if (lex.lookAhead(i++) != ']') + return -1; + + return i - 1; + } + + /* array.dimension : [ "[" "]" ]* + */ + private int parseArrayDimension() throws CompileError { + int arrayDim = 0; + while (lex.lookAhead() == '[') { + ++arrayDim; + lex.get(); + if (lex.get() != ']') + throw new CompileError("] is missing", lex); + } + + return arrayDim; + } + + /* class.type : Identifier ( "." Identifier )* + */ + private ASTList parseClassType(SymbolTable tbl) throws CompileError { + ASTList list = null; + for (;;) { + if (lex.get() != Identifier) + throw new SyntaxError(lex); + + list = ASTList.append(list, new Symbol(lex.getString())); + if (lex.lookAhead() == '.') + lex.get(); + else + break; + } + + return list; + } + + /* postfix.expr : number.literal + * | primary.expr + * | method.expr + * | postfix.expr "++" | "--" + * | postfix.expr "[" array.size "]" + * | postfix.expr "." Identifier + * | postfix.expr ( "[" "]" )* "." CLASS + * | postfix.expr "#" Identifier + * + * "#" is not an operator of regular Java. It separates + * a class name and a member name in an expression for static member + * access. For example, + * java.lang.Integer.toString(3) in regular Java + * can be written like this: + * java.lang.Integer#toString(3) for this compiler. + */ + private ASTree parsePostfix(SymbolTable tbl) throws CompileError { + int token = lex.lookAhead(); + switch (token) { // see also parseUnaryExpr() + case LongConstant : + case IntConstant : + case CharConstant : + lex.get(); + return new IntConst(lex.getLong(), token); + case DoubleConstant : + case FloatConstant : + lex.get(); + return new DoubleConst(lex.getDouble(), token); + default : + break; + } + + String str; + ASTree index; + ASTree expr = parsePrimaryExpr(tbl); + int t; + while (true) { + switch (lex.lookAhead()) { + case '(' : + expr = parseMethodCall(tbl, expr); + break; + case '[' : + if (lex.lookAhead(1) == ']') { + int dim = parseArrayDimension(); + if (lex.get() != '.' || lex.get() != CLASS) + throw new SyntaxError(lex); + + expr = parseDotClass(expr, dim); + } + else { + index = parseArrayIndex(tbl); + if (index == null) + throw new SyntaxError(lex); + + expr = Expr.make(ARRAY, expr, index); + } + break; + case PLUSPLUS : + case MINUSMINUS : + t = lex.get(); + expr = Expr.make(t, null, expr); + break; + case '.' : + lex.get(); + t = lex.get(); + if (t == CLASS) { + expr = parseDotClass(expr, 0); + } + else if (t == Identifier) { + str = lex.getString(); + expr = Expr.make('.', expr, new Member(str)); + } + else + throw new CompileError("missing member name", lex); + break; + case '#' : + lex.get(); + t = lex.get(); + if (t != Identifier) + throw new CompileError("missing static member name", lex); + + str = lex.getString(); + expr = Expr.make(MEMBER, new Symbol(toClassName(expr)), + new Member(str)); + break; + default : + return expr; + } + } + } + + /* Parse a .class expression on a class type. For example, + * String.class => ('.' "String" "class") + * String[].class => ('.' "[LString;" "class") + */ + private ASTree parseDotClass(ASTree className, int dim) + throws CompileError + { + String cname = toClassName(className); + if (dim > 0) { + StringBuffer sbuf = new StringBuffer(); + while (dim-- > 0) + sbuf.append('['); + + sbuf.append('L').append(cname.replace('.', '/')).append(';'); + cname = sbuf.toString(); + } + + return Expr.make('.', new Symbol(cname), new Member("class")); + } + + /* Parses a .class expression on a built-in type. For example, + * int.class => ('#' "java.lang.Integer" "TYPE") + * int[].class => ('.' "[I", "class") + */ + private ASTree parseDotClass(int builtinType, int dim) + throws CompileError + { + if (dim > 0) { + String cname = CodeGen.toJvmTypeName(builtinType, dim); + return Expr.make('.', new Symbol(cname), new Member("class")); + } + else { + String cname; + switch(builtinType) { + case BOOLEAN : + cname = "java.lang.Boolean"; + break; + case BYTE : + cname = "java.lang.Byte"; + break; + case CHAR : + cname = "java.lang.Character"; + break; + case SHORT : + cname = "java.lang.Short"; + break; + case INT : + cname = "java.lang.Integer"; + break; + case LONG : + cname = "java.lang.Long"; + break; + case FLOAT : + cname = "java.lang.Float"; + break; + case DOUBLE : + cname = "java.lang.Double"; + break; + case VOID : + cname = "java.lang.Void"; + break; + default : + throw new CompileError("invalid builtin type: " + + builtinType); + } + + return Expr.make(MEMBER, new Symbol(cname), new Member("TYPE")); + } + } + + /* method.call : method.expr "(" argument.list ")" + * method.expr : THIS | SUPER | Identifier + * | postfix.expr "." Identifier + * | postfix.expr "#" Identifier + */ + private ASTree parseMethodCall(SymbolTable tbl, ASTree expr) + throws CompileError + { + if (expr instanceof Keyword) { + int token = ((Keyword)expr).get(); + if (token != THIS && token != SUPER) + throw new SyntaxError(lex); + } + else if (expr instanceof Symbol) // Identifier + ; + else if (expr instanceof Expr) { + int op = ((Expr)expr).getOperator(); + if (op != '.' && op != MEMBER) + throw new SyntaxError(lex); + } + + return CallExpr.makeCall(expr, parseArgumentList(tbl)); + } + + private String toClassName(ASTree name) + throws CompileError + { + StringBuffer sbuf = new StringBuffer(); + toClassName(name, sbuf); + return sbuf.toString(); + } + + private void toClassName(ASTree name, StringBuffer sbuf) + throws CompileError + { + if (name instanceof Symbol) { + sbuf.append(((Symbol)name).get()); + return; + } + else if (name instanceof Expr) { + Expr expr = (Expr)name; + if (expr.getOperator() == '.') { + toClassName(expr.oprand1(), sbuf); + sbuf.append('.'); + toClassName(expr.oprand2(), sbuf); + return; + } + } + + throw new CompileError("bad static member access", lex); + } + + /* primary.expr : THIS | SUPER | TRUE | FALSE | NULL + * | StringL + * | Identifier + * | NEW new.expr + * | "(" expression ")" + * | builtin.type ( "[" "]" )* "." CLASS + * + * Identifier represents either a local variable name, a member name, + * or a class name. + */ + private ASTree parsePrimaryExpr(SymbolTable tbl) throws CompileError { + int t; + String name; + Declarator decl; + ASTree expr; + + switch (t = lex.get()) { + case THIS : + case SUPER : + case TRUE : + case FALSE : + case NULL : + return new Keyword(t); + case Identifier : + name = lex.getString(); + decl = tbl.lookup(name); + if (decl == null) + return new Member(name); // this or static member + else + return new Variable(name, decl); // local variable + case StringL : + return new StringL(lex.getString()); + case NEW : + return parseNew(tbl); + case '(' : + expr = parseExpression(tbl); + if (lex.get() == ')') + return expr; + else + throw new CompileError(") is missing", lex); + default : + if (isBuiltinType(t) || t == VOID) { + int dim = parseArrayDimension(); + if (lex.get() == '.' && lex.get() == CLASS) + return parseDotClass(t, dim); + } + + throw new SyntaxError(lex); + } + } + + /* new.expr : class.type "(" argument.list ")" + * | class.type array.size [ array.initializer ] + * | primitive.type array.size [ array.initializer ] + */ + private NewExpr parseNew(SymbolTable tbl) throws CompileError { + ArrayInit init = null; + int t = lex.lookAhead(); + if (isBuiltinType(t)) { + lex.get(); + ASTList size = parseArraySize(tbl); + if (lex.lookAhead() == '{') + init = parseArrayInitializer(tbl); + + return new NewExpr(t, size, init); + } + else if (t == Identifier) { + ASTList name = parseClassType(tbl); + t = lex.lookAhead(); + if (t == '(') { + ASTList args = parseArgumentList(tbl); + return new NewExpr(name, args); + } + else if (t == '[') { + ASTList size = parseArraySize(tbl); + if (lex.lookAhead() == '{') + init = parseArrayInitializer(tbl); + + return NewExpr.makeObjectArray(name, size, init); + } + } + + throw new SyntaxError(lex); + } + + /* array.size : [ array.index ]* + */ + private ASTList parseArraySize(SymbolTable tbl) throws CompileError { + ASTList list = null; + while (lex.lookAhead() == '[') + list = ASTList.append(list, parseArrayIndex(tbl)); + + return list; + } + + /* array.index : "[" [ expression ] "]" + */ + private ASTree parseArrayIndex(SymbolTable tbl) throws CompileError { + lex.get(); // '[' + if (lex.lookAhead() == ']') { + lex.get(); + return null; + } + else { + ASTree index = parseExpression(tbl); + if (lex.get() != ']') + throw new CompileError("] is missing", lex); + + return index; + } + } + + /* argument.list : "(" [ expression [ "," expression ]* ] ")" + */ + private ASTList parseArgumentList(SymbolTable tbl) throws CompileError { + if (lex.get() != '(') + throw new CompileError("( is missing", lex); + + ASTList list = null; + if (lex.lookAhead() != ')') + for (;;) { + list = ASTList.append(list, parseExpression(tbl)); + if (lex.lookAhead() == ',') + lex.get(); + else + break; + } + + if (lex.get() != ')') + throw new CompileError(") is missing", lex); + + return list; + } +} + diff --git a/src/main/javassist/compiler/ProceedHandler.java b/src/main/javassist/compiler/ProceedHandler.java new file mode 100644 index 0000000..fd77f0e --- /dev/null +++ b/src/main/javassist/compiler/ProceedHandler.java @@ -0,0 +1,30 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.compiler; + +import javassist.bytecode.Bytecode; +import javassist.compiler.ast.ASTList; + +/** + * An interface to an object for implementing $proceed(). + * + * @see javassist.compiler.JvstCodeGen#setProceedHandler(ProceedHandler, String) + * @see javassist.compiler.JvstCodeGen#atMethodCall(Expr) + */ +public interface ProceedHandler { + void doit(JvstCodeGen gen, Bytecode b, ASTList args) throws CompileError; + void setReturnType(JvstTypeChecker c, ASTList args) throws CompileError; +} diff --git a/src/main/javassist/compiler/SymbolTable.java b/src/main/javassist/compiler/SymbolTable.java new file mode 100644 index 0000000..8f35f36 --- /dev/null +++ b/src/main/javassist/compiler/SymbolTable.java @@ -0,0 +1,44 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.compiler; + +import java.util.HashMap; +import javassist.compiler.ast.Declarator; + +public final class SymbolTable extends HashMap { + private SymbolTable parent; + + public SymbolTable() { this(null); } + + public SymbolTable(SymbolTable p) { + super(); + parent = p; + } + + public SymbolTable getParent() { return parent; } + + public Declarator lookup(String name) { + Declarator found = (Declarator)get(name); + if (found == null && parent != null) + return parent.lookup(name); + else + return found; + } + + public void append(String name, Declarator value) { + put(name, value); + } +} diff --git a/src/main/javassist/compiler/SyntaxError.java b/src/main/javassist/compiler/SyntaxError.java new file mode 100644 index 0000000..dcbb937 --- /dev/null +++ b/src/main/javassist/compiler/SyntaxError.java @@ -0,0 +1,22 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.compiler; + +public class SyntaxError extends CompileError { + public SyntaxError(Lex lexer) { + super("syntax error near \"" + lexer.getTextAround() + "\"", lexer); + } +} diff --git a/src/main/javassist/compiler/TokenId.java b/src/main/javassist/compiler/TokenId.java new file mode 100644 index 0000000..6a94768 --- /dev/null +++ b/src/main/javassist/compiler/TokenId.java @@ -0,0 +1,124 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.compiler; + +public interface TokenId { + int ABSTRACT = 300; + int BOOLEAN = 301; + int BREAK = 302; + int BYTE = 303; + int CASE = 304; + int CATCH = 305; + int CHAR = 306; + int CLASS = 307; + int CONST = 308; // reserved keyword + int CONTINUE = 309; + int DEFAULT = 310; + int DO = 311; + int DOUBLE = 312; + int ELSE = 313; + int EXTENDS = 314; + int FINAL = 315; + int FINALLY = 316; + int FLOAT = 317; + int FOR = 318; + int GOTO = 319; // reserved keyword + int IF = 320; + int IMPLEMENTS = 321; + int IMPORT = 322; + int INSTANCEOF = 323; + int INT = 324; + int INTERFACE = 325; + int LONG = 326; + int NATIVE = 327; + int NEW = 328; + int PACKAGE = 329; + int PRIVATE = 330; + int PROTECTED = 331; + int PUBLIC = 332; + int RETURN = 333; + int SHORT = 334; + int STATIC = 335; + int SUPER = 336; + int SWITCH = 337; + int SYNCHRONIZED = 338; + int THIS = 339; + int THROW = 340; + int THROWS = 341; + int TRANSIENT = 342; + int TRY = 343; + int VOID = 344; + int VOLATILE = 345; + int WHILE = 346; + int STRICT = 347; + + int NEQ = 350; // != + int MOD_E = 351; // %= + int AND_E = 352; // &= + int MUL_E = 353; // *= + int PLUS_E = 354; // += + int MINUS_E = 355; // -= + int DIV_E = 356; // /= + int LE = 357; // <= + int EQ = 358; // == + int GE = 359; // >= + int EXOR_E = 360; // ^= + int OR_E = 361; // |= + int PLUSPLUS = 362; // ++ + int MINUSMINUS = 363; // -- + int LSHIFT = 364; // << + int LSHIFT_E = 365; // <<= + int RSHIFT = 366; // >> + int RSHIFT_E = 367; // >>= + int OROR = 368; // || + int ANDAND = 369; // && + int ARSHIFT = 370; // >>> + int ARSHIFT_E = 371; // >>>= + + // operators from NEQ to ARSHIFT_E + String opNames[] = { "!=", "%=", "&=", "*=", "+=", "-=", "/=", + "<=", "==", ">=", "^=", "|=", "++", "--", + "<<", "<<=", ">>", ">>=", "||", "&&", ">>>", + ">>>=" }; + + // operators from MOD_E to ARSHIFT_E + int assignOps[] = { '%', '&', '*', '+', '-', '/', 0, 0, 0, + '^', '|', 0, 0, 0, LSHIFT, 0, RSHIFT, 0, 0, 0, + ARSHIFT }; + + int Identifier = 400; + int CharConstant = 401; + int IntConstant = 402; + int LongConstant = 403; + int FloatConstant = 404; + int DoubleConstant = 405; + int StringL = 406; + + int TRUE = 410; + int FALSE = 411; + int NULL = 412; + + int CALL = 'C'; // method call + int ARRAY = 'A'; // array access + int MEMBER = '#'; // static member access + + int EXPR = 'E'; // expression statement + int LABEL = 'L'; // label statement + int BLOCK = 'B'; // block statement + int DECL = 'D'; // declaration statement + + int BadToken = 500; +} diff --git a/src/main/javassist/compiler/TypeChecker.java b/src/main/javassist/compiler/TypeChecker.java new file mode 100644 index 0000000..d2cb861 --- /dev/null +++ b/src/main/javassist/compiler/TypeChecker.java @@ -0,0 +1,1008 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.compiler; + +import javassist.CtClass; +import javassist.CtField; +import javassist.ClassPool; +import javassist.Modifier; +import javassist.NotFoundException; +import javassist.compiler.ast.*; +import javassist.bytecode.*; + +public class TypeChecker extends Visitor implements Opcode, TokenId { + static final String javaLangObject = "java.lang.Object"; + static final String jvmJavaLangObject = "java/lang/Object"; + static final String jvmJavaLangString = "java/lang/String"; + static final String jvmJavaLangClass = "java/lang/Class"; + + /* The following fields are used by atXXX() methods + * for returning the type of the compiled expression. + */ + protected int exprType; // VOID, NULL, CLASS, BOOLEAN, INT, ... + protected int arrayDim; + protected String className; // JVM-internal representation + + protected MemberResolver resolver; + protected CtClass thisClass; + protected MethodInfo thisMethod; + + public TypeChecker(CtClass cc, ClassPool cp) { + resolver = new MemberResolver(cp); + thisClass = cc; + thisMethod = null; + } + + /* + * Converts an array of tuples of exprType, arrayDim, and className + * into a String object. + */ + protected static String argTypesToString(int[] types, int[] dims, + String[] cnames) { + StringBuffer sbuf = new StringBuffer(); + sbuf.append('('); + int n = types.length; + if (n > 0) { + int i = 0; + while (true) { + typeToString(sbuf, types[i], dims[i], cnames[i]); + if (++i < n) + sbuf.append(','); + else + break; + } + } + + sbuf.append(')'); + return sbuf.toString(); + } + + /* + * Converts a tuple of exprType, arrayDim, and className + * into a String object. + */ + protected static StringBuffer typeToString(StringBuffer sbuf, + int type, int dim, String cname) { + String s; + if (type == CLASS) + s = MemberResolver.jvmToJavaName(cname); + else if (type == NULL) + s = "Object"; + else + try { + s = MemberResolver.getTypeName(type); + } + catch (CompileError e) { + s = "?"; + } + + sbuf.append(s); + while (dim-- > 0) + sbuf.append("[]"); + + return sbuf; + } + + /** + * Records the currently compiled method. + */ + public void setThisMethod(MethodInfo m) { + thisMethod = m; + } + + protected static void fatal() throws CompileError { + throw new CompileError("fatal"); + } + + /** + * Returns the JVM-internal representation of this class name. + */ + protected String getThisName() { + return MemberResolver.javaToJvmName(thisClass.getName()); + } + + /** + * Returns the JVM-internal representation of this super class name. + */ + protected String getSuperName() throws CompileError { + return MemberResolver.javaToJvmName( + MemberResolver.getSuperclass(thisClass).getName()); + } + + /* Converts a class name into a JVM-internal representation. + * + * It may also expand a simple class name to java.lang.*. + * For example, this converts Object into java/lang/Object. + */ + protected String resolveClassName(ASTList name) throws CompileError { + return resolver.resolveClassName(name); + } + + /* Expands a simple class name to java.lang.*. + * For example, this converts Object into java/lang/Object. + */ + protected String resolveClassName(String jvmName) throws CompileError { + return resolver.resolveJvmClassName(jvmName); + } + + public void atNewExpr(NewExpr expr) throws CompileError { + if (expr.isArray()) + atNewArrayExpr(expr); + else { + CtClass clazz = resolver.lookupClassByName(expr.getClassName()); + String cname = clazz.getName(); + ASTList args = expr.getArguments(); + atMethodCallCore(clazz, MethodInfo.nameInit, args); + exprType = CLASS; + arrayDim = 0; + className = MemberResolver.javaToJvmName(cname); + } + } + + public void atNewArrayExpr(NewExpr expr) throws CompileError { + int type = expr.getArrayType(); + ASTList size = expr.getArraySize(); + ASTList classname = expr.getClassName(); + ASTree init = expr.getInitializer(); + if (init != null) + init.accept(this); + + if (size.length() > 1) + atMultiNewArray(type, classname, size); + else { + ASTree sizeExpr = size.head(); + if (sizeExpr != null) + sizeExpr.accept(this); + + exprType = type; + arrayDim = 1; + if (type == CLASS) + className = resolveClassName(classname); + else + className = null; + } + } + + public void atArrayInit(ArrayInit init) throws CompileError { + ASTList list = init; + while (list != null) { + ASTree h = list.head(); + list = list.tail(); + if (h != null) + h.accept(this); + } + } + + protected void atMultiNewArray(int type, ASTList classname, ASTList size) + throws CompileError + { + int count, dim; + dim = size.length(); + for (count = 0; size != null; size = size.tail()) { + ASTree s = size.head(); + if (s == null) + break; // int[][][] a = new int[3][4][]; + + ++count; + s.accept(this); + } + + exprType = type; + arrayDim = dim; + if (type == CLASS) + className = resolveClassName(classname); + else + className = null; + } + + public void atAssignExpr(AssignExpr expr) throws CompileError { + // =, %=, &=, *=, /=, +=, -=, ^=, |=, <<=, >>=, >>>= + int op = expr.getOperator(); + ASTree left = expr.oprand1(); + ASTree right = expr.oprand2(); + if (left instanceof Variable) + atVariableAssign(expr, op, (Variable)left, + ((Variable)left).getDeclarator(), + right); + else { + if (left instanceof Expr) { + Expr e = (Expr)left; + if (e.getOperator() == ARRAY) { + atArrayAssign(expr, op, (Expr)left, right); + return; + } + } + + atFieldAssign(expr, op, left, right); + } + } + + /* op is either =, %=, &=, *=, /=, +=, -=, ^=, |=, <<=, >>=, or >>>=. + * + * expr and var can be null. + */ + private void atVariableAssign(Expr expr, int op, Variable var, + Declarator d, ASTree right) + throws CompileError + { + int varType = d.getType(); + int varArray = d.getArrayDim(); + String varClass = d.getClassName(); + + if (op != '=') + atVariable(var); + + right.accept(this); + exprType = varType; + arrayDim = varArray; + className = varClass; + } + + private void atArrayAssign(Expr expr, int op, Expr array, + ASTree right) throws CompileError + { + atArrayRead(array.oprand1(), array.oprand2()); + int aType = exprType; + int aDim = arrayDim; + String cname = className; + right.accept(this); + exprType = aType; + arrayDim = aDim; + className = cname; + } + + protected void atFieldAssign(Expr expr, int op, ASTree left, ASTree right) + throws CompileError + { + CtField f = fieldAccess(left); + atFieldRead(f); + int fType = exprType; + int fDim = arrayDim; + String cname = className; + right.accept(this); + exprType = fType; + arrayDim = fDim; + className = cname; + } + + public void atCondExpr(CondExpr expr) throws CompileError { + booleanExpr(expr.condExpr()); + expr.thenExpr().accept(this); + int type1 = exprType; + int dim1 = arrayDim; + String cname1 = className; + expr.elseExpr().accept(this); + + if (dim1 == 0 && dim1 == arrayDim) + if (CodeGen.rightIsStrong(type1, exprType)) + expr.setThen(new CastExpr(exprType, 0, expr.thenExpr())); + else if (CodeGen.rightIsStrong(exprType, type1)) { + expr.setElse(new CastExpr(type1, 0, expr.elseExpr())); + exprType = type1; + } + } + + /* + * If atBinExpr() substitutes a new expression for the original + * binary-operator expression, it changes the operator name to '+' + * (if the original is not '+') and sets the new expression to the + * left-hand-side expression and null to the right-hand-side expression. + */ + public void atBinExpr(BinExpr expr) throws CompileError { + int token = expr.getOperator(); + int k = CodeGen.lookupBinOp(token); + if (k >= 0) { + /* arithmetic operators: +, -, *, /, %, |, ^, &, <<, >>, >>> + */ + if (token == '+') { + Expr e = atPlusExpr(expr); + if (e != null) { + /* String concatenation has been translated into + * an expression using StringBuffer. + */ + e = CallExpr.makeCall(Expr.make('.', e, + new Member("toString")), null); + expr.setOprand1(e); + expr.setOprand2(null); // <---- look at this! + className = jvmJavaLangString; + } + } + else { + ASTree left = expr.oprand1(); + ASTree right = expr.oprand2(); + left.accept(this); + int type1 = exprType; + right.accept(this); + if (!isConstant(expr, token, left, right)) + computeBinExprType(expr, token, type1); + } + } + else { + /* equation: &&, ||, ==, !=, <=, >=, <, > + */ + booleanExpr(expr); + } + } + + /* EXPR must be a + expression. + * atPlusExpr() returns non-null if the given expression is string + * concatenation. The returned value is "new StringBuffer().append..". + */ + private Expr atPlusExpr(BinExpr expr) throws CompileError { + ASTree left = expr.oprand1(); + ASTree right = expr.oprand2(); + if (right == null) { + // this expression has been already type-checked. + // see atBinExpr() above. + left.accept(this); + return null; + } + + if (isPlusExpr(left)) { + Expr newExpr = atPlusExpr((BinExpr)left); + if (newExpr != null) { + right.accept(this); + exprType = CLASS; + arrayDim = 0; + className = "java/lang/StringBuffer"; + return makeAppendCall(newExpr, right); + } + } + else + left.accept(this); + + int type1 = exprType; + int dim1 = arrayDim; + String cname = className; + right.accept(this); + + if (isConstant(expr, '+', left, right)) + return null; + + if ((type1 == CLASS && dim1 == 0 && jvmJavaLangString.equals(cname)) + || (exprType == CLASS && arrayDim == 0 + && jvmJavaLangString.equals(className))) { + ASTList sbufClass = ASTList.make(new Symbol("java"), + new Symbol("lang"), new Symbol("StringBuffer")); + ASTree e = new NewExpr(sbufClass, null); + exprType = CLASS; + arrayDim = 0; + className = "java/lang/StringBuffer"; + return makeAppendCall(makeAppendCall(e, left), right); + } + else { + computeBinExprType(expr, '+', type1); + return null; + } + } + + private boolean isConstant(BinExpr expr, int op, ASTree left, + ASTree right) throws CompileError + { + left = stripPlusExpr(left); + right = stripPlusExpr(right); + ASTree newExpr = null; + if (left instanceof StringL && right instanceof StringL && op == '+') + newExpr = new StringL(((StringL)left).get() + + ((StringL)right).get()); + else if (left instanceof IntConst) + newExpr = ((IntConst)left).compute(op, right); + else if (left instanceof DoubleConst) + newExpr = ((DoubleConst)left).compute(op, right); + + if (newExpr == null) + return false; // not a constant expression + else { + expr.setOperator('+'); + expr.setOprand1(newExpr); + expr.setOprand2(null); + newExpr.accept(this); // for setting exprType, arrayDim, ... + return true; + } + } + + /* CodeGen.atSwitchStmnt() also calls stripPlusExpr(). + */ + static ASTree stripPlusExpr(ASTree expr) { + if (expr instanceof BinExpr) { + BinExpr e = (BinExpr)expr; + if (e.getOperator() == '+' && e.oprand2() == null) + return e.getLeft(); + } + else if (expr instanceof Expr) { // note: BinExpr extends Expr. + Expr e = (Expr)expr; + int op = e.getOperator(); + if (op == MEMBER) { + ASTree cexpr = getConstantFieldValue((Member)e.oprand2()); + if (cexpr != null) + return cexpr; + } + else if (op == '+' && e.getRight() == null) + return e.getLeft(); + } + else if (expr instanceof Member) { + ASTree cexpr = getConstantFieldValue((Member)expr); + if (cexpr != null) + return cexpr; + } + + return expr; + } + + /** + * If MEM is a static final field, this method returns a constant + * expression representing the value of that field. + */ + private static ASTree getConstantFieldValue(Member mem) { + return getConstantFieldValue(mem.getField()); + } + + public static ASTree getConstantFieldValue(CtField f) { + if (f == null) + return null; + + Object value = f.getConstantValue(); + if (value == null) + return null; + + if (value instanceof String) + return new StringL((String)value); + else if (value instanceof Double || value instanceof Float) { + int token = (value instanceof Double) + ? DoubleConstant : FloatConstant; + return new DoubleConst(((Number)value).doubleValue(), token); + } + else if (value instanceof Number) { + int token = (value instanceof Long) ? LongConstant : IntConstant; + return new IntConst(((Number)value).longValue(), token); + } + else if (value instanceof Boolean) + return new Keyword(((Boolean)value).booleanValue() + ? TokenId.TRUE : TokenId.FALSE); + else + return null; + } + + private static boolean isPlusExpr(ASTree expr) { + if (expr instanceof BinExpr) { + BinExpr bexpr = (BinExpr)expr; + int token = bexpr.getOperator(); + return token == '+'; + } + + return false; + } + + private static Expr makeAppendCall(ASTree target, ASTree arg) { + return CallExpr.makeCall(Expr.make('.', target, new Member("append")), + new ASTList(arg)); + } + + private void computeBinExprType(BinExpr expr, int token, int type1) + throws CompileError + { + // arrayDim should be 0. + int type2 = exprType; + if (token == LSHIFT || token == RSHIFT || token == ARSHIFT) + exprType = type1; + else + insertCast(expr, type1, type2); + + if (CodeGen.isP_INT(exprType)) + exprType = INT; // type1 may be BYTE, ... + } + + private void booleanExpr(ASTree expr) + throws CompileError + { + int op = CodeGen.getCompOperator(expr); + if (op == EQ) { // ==, !=, ... + BinExpr bexpr = (BinExpr)expr; + bexpr.oprand1().accept(this); + int type1 = exprType; + int dim1 = arrayDim; + bexpr.oprand2().accept(this); + if (dim1 == 0 && arrayDim == 0) + insertCast(bexpr, type1, exprType); + } + else if (op == '!') + ((Expr)expr).oprand1().accept(this); + else if (op == ANDAND || op == OROR) { + BinExpr bexpr = (BinExpr)expr; + bexpr.oprand1().accept(this); + bexpr.oprand2().accept(this); + } + else // others + expr.accept(this); + + exprType = BOOLEAN; + arrayDim = 0; + } + + private void insertCast(BinExpr expr, int type1, int type2) + throws CompileError + { + if (CodeGen.rightIsStrong(type1, type2)) + expr.setLeft(new CastExpr(type2, 0, expr.oprand1())); + else + exprType = type1; + } + + public void atCastExpr(CastExpr expr) throws CompileError { + String cname = resolveClassName(expr.getClassName()); + expr.getOprand().accept(this); + exprType = expr.getType(); + arrayDim = expr.getArrayDim(); + className = cname; + } + + public void atInstanceOfExpr(InstanceOfExpr expr) throws CompileError { + expr.getOprand().accept(this); + exprType = BOOLEAN; + arrayDim = 0; + } + + public void atExpr(Expr expr) throws CompileError { + // array access, member access, + // (unary) +, (unary) -, ++, --, !, ~ + + int token = expr.getOperator(); + ASTree oprand = expr.oprand1(); + if (token == '.') { + String member = ((Symbol)expr.oprand2()).get(); + if (member.equals("length")) + atArrayLength(expr); + else if (member.equals("class")) + atClassObject(expr); // .class + else + atFieldRead(expr); + } + else if (token == MEMBER) { // field read + String member = ((Symbol)expr.oprand2()).get(); + if (member.equals("class")) + atClassObject(expr); // .class + else + atFieldRead(expr); + } + else if (token == ARRAY) + atArrayRead(oprand, expr.oprand2()); + else if (token == PLUSPLUS || token == MINUSMINUS) + atPlusPlus(token, oprand, expr); + else if (token == '!') + booleanExpr(expr); + else if (token == CALL) // method call + fatal(); + else { + oprand.accept(this); + if (!isConstant(expr, token, oprand)) + if (token == '-' || token == '~') + if (CodeGen.isP_INT(exprType)) + exprType = INT; // type may be BYTE, ... + } + } + + private boolean isConstant(Expr expr, int op, ASTree oprand) { + oprand = stripPlusExpr(oprand); + if (oprand instanceof IntConst) { + IntConst c = (IntConst)oprand; + long v = c.get(); + if (op == '-') + v = -v; + else if (op == '~') + v = ~v; + else + return false; + + c.set(v); + } + else if (oprand instanceof DoubleConst) { + DoubleConst c = (DoubleConst)oprand; + if (op == '-') + c.set(-c.get()); + else + return false; + } + else + return false; + + expr.setOperator('+'); + return true; + } + + public void atCallExpr(CallExpr expr) throws CompileError { + String mname = null; + CtClass targetClass = null; + ASTree method = expr.oprand1(); + ASTList args = (ASTList)expr.oprand2(); + + if (method instanceof Member) { + mname = ((Member)method).get(); + targetClass = thisClass; + } + else if (method instanceof Keyword) { // constructor + mname = MethodInfo.nameInit; // <init> + if (((Keyword)method).get() == SUPER) + targetClass = MemberResolver.getSuperclass(thisClass); + else + targetClass = thisClass; + } + else if (method instanceof Expr) { + Expr e = (Expr)method; + mname = ((Symbol)e.oprand2()).get(); + int op = e.getOperator(); + if (op == MEMBER) // static method + targetClass + = resolver.lookupClass(((Symbol)e.oprand1()).get(), + false); + else if (op == '.') { + ASTree target = e.oprand1(); + try { + target.accept(this); + } + catch (NoFieldException nfe) { + if (nfe.getExpr() != target) + throw nfe; + + // it should be a static method. + exprType = CLASS; + arrayDim = 0; + className = nfe.getField(); // JVM-internal + e.setOperator(MEMBER); + e.setOprand1(new Symbol(MemberResolver.jvmToJavaName( + className))); + } + + if (arrayDim > 0) + targetClass = resolver.lookupClass(javaLangObject, true); + else if (exprType == CLASS /* && arrayDim == 0 */) + targetClass = resolver.lookupClassByJvmName(className); + else + badMethod(); + } + else + badMethod(); + } + else + fatal(); + + MemberResolver.Method minfo + = atMethodCallCore(targetClass, mname, args); + expr.setMethod(minfo); + } + + private static void badMethod() throws CompileError { + throw new CompileError("bad method"); + } + + /** + * @return a pair of the class declaring the invoked method + * and the MethodInfo of that method. Never null. + */ + public MemberResolver.Method atMethodCallCore(CtClass targetClass, + String mname, ASTList args) + throws CompileError + { + int nargs = getMethodArgsLength(args); + int[] types = new int[nargs]; + int[] dims = new int[nargs]; + String[] cnames = new String[nargs]; + atMethodArgs(args, types, dims, cnames); + + MemberResolver.Method found + = resolver.lookupMethod(targetClass, thisClass, thisMethod, + mname, types, dims, cnames); + if (found == null) { + String clazz = targetClass.getName(); + String signature = argTypesToString(types, dims, cnames); + String msg; + if (mname.equals(MethodInfo.nameInit)) + msg = "cannot find constructor " + clazz + signature; + else + msg = mname + signature + " not found in " + clazz; + + throw new CompileError(msg); + } + + String desc = found.info.getDescriptor(); + setReturnType(desc); + return found; + } + + public int getMethodArgsLength(ASTList args) { + return ASTList.length(args); + } + + public void atMethodArgs(ASTList args, int[] types, int[] dims, + String[] cnames) throws CompileError { + int i = 0; + while (args != null) { + ASTree a = args.head(); + a.accept(this); + types[i] = exprType; + dims[i] = arrayDim; + cnames[i] = className; + ++i; + args = args.tail(); + } + } + + void setReturnType(String desc) throws CompileError { + int i = desc.indexOf(')'); + if (i < 0) + badMethod(); + + char c = desc.charAt(++i); + int dim = 0; + while (c == '[') { + ++dim; + c = desc.charAt(++i); + } + + arrayDim = dim; + if (c == 'L') { + int j = desc.indexOf(';', i + 1); + if (j < 0) + badMethod(); + + exprType = CLASS; + className = desc.substring(i + 1, j); + } + else { + exprType = MemberResolver.descToType(c); + className = null; + } + } + + private void atFieldRead(ASTree expr) throws CompileError { + atFieldRead(fieldAccess(expr)); + } + + private void atFieldRead(CtField f) throws CompileError { + FieldInfo finfo = f.getFieldInfo2(); + String type = finfo.getDescriptor(); + + int i = 0; + int dim = 0; + char c = type.charAt(i); + while (c == '[') { + ++dim; + c = type.charAt(++i); + } + + arrayDim = dim; + exprType = MemberResolver.descToType(c); + + if (c == 'L') + className = type.substring(i + 1, type.indexOf(';', i + 1)); + else + className = null; + } + + /* if EXPR is to access a static field, fieldAccess() translates EXPR + * into an expression using '#' (MEMBER). For example, it translates + * java.lang.Integer.TYPE into java.lang.Integer#TYPE. This translation + * speeds up type resolution by MemberCodeGen. + */ + protected CtField fieldAccess(ASTree expr) throws CompileError { + if (expr instanceof Member) { + Member mem = (Member)expr; + String name = mem.get(); + try { + CtField f = thisClass.getField(name); + if (Modifier.isStatic(f.getModifiers())) + mem.setField(f); + + return f; + } + catch (NotFoundException e) { + // EXPR might be part of a static member access? + throw new NoFieldException(name, expr); + } + } + else if (expr instanceof Expr) { + Expr e = (Expr)expr; + int op = e.getOperator(); + if (op == MEMBER) { + Member mem = (Member)e.oprand2(); + CtField f + = resolver.lookupField(((Symbol)e.oprand1()).get(), mem); + mem.setField(f); + return f; + } + else if (op == '.') { + try { + e.oprand1().accept(this); + } + catch (NoFieldException nfe) { + if (nfe.getExpr() != e.oprand1()) + throw nfe; + + /* EXPR should be a static field. + * If EXPR might be part of a qualified class name, + * lookupFieldByJvmName2() throws NoFieldException. + */ + return fieldAccess2(e, nfe.getField()); + } + + CompileError err = null; + try { + if (exprType == CLASS && arrayDim == 0) + return resolver.lookupFieldByJvmName(className, + (Symbol)e.oprand2()); + } + catch (CompileError ce) { + err = ce; + } + + /* If a filed name is the same name as a package's, + * a static member of a class in that package is not + * visible. For example, + * + * class Foo { + * int javassist; + * } + * + * It is impossible to add the following method: + * + * String m() { return javassist.CtClass.intType.toString(); } + * + * because javassist is a field name. However, this is + * often inconvenient, this compiler allows it. The following + * code is for that. + */ + ASTree oprnd1 = e.oprand1(); + if (oprnd1 instanceof Symbol) + return fieldAccess2(e, ((Symbol)oprnd1).get()); + + if (err != null) + throw err; + } + } + + throw new CompileError("bad filed access"); + } + + private CtField fieldAccess2(Expr e, String jvmClassName) throws CompileError { + Member fname = (Member)e.oprand2(); + CtField f = resolver.lookupFieldByJvmName2(jvmClassName, fname, e); + e.setOperator(MEMBER); + e.setOprand1(new Symbol(MemberResolver.jvmToJavaName(jvmClassName))); + fname.setField(f); + return f; + } + + public void atClassObject(Expr expr) throws CompileError { + exprType = CLASS; + arrayDim = 0; + className =jvmJavaLangClass; + } + + public void atArrayLength(Expr expr) throws CompileError { + expr.oprand1().accept(this); + exprType = INT; + arrayDim = 0; + } + + public void atArrayRead(ASTree array, ASTree index) + throws CompileError + { + array.accept(this); + int type = exprType; + int dim = arrayDim; + String cname = className; + index.accept(this); + exprType = type; + arrayDim = dim - 1; + className = cname; + } + + private void atPlusPlus(int token, ASTree oprand, Expr expr) + throws CompileError + { + boolean isPost = oprand == null; // ++i or i++? + if (isPost) + oprand = expr.oprand2(); + + if (oprand instanceof Variable) { + Declarator d = ((Variable)oprand).getDeclarator(); + exprType = d.getType(); + arrayDim = d.getArrayDim(); + } + else { + if (oprand instanceof Expr) { + Expr e = (Expr)oprand; + if (e.getOperator() == ARRAY) { + atArrayRead(e.oprand1(), e.oprand2()); + // arrayDim should be 0. + int t = exprType; + if (t == INT || t == BYTE || t == CHAR || t == SHORT) + exprType = INT; + + return; + } + } + + atFieldPlusPlus(oprand); + } + } + + protected void atFieldPlusPlus(ASTree oprand) throws CompileError + { + CtField f = fieldAccess(oprand); + atFieldRead(f); + int t = exprType; + if (t == INT || t == BYTE || t == CHAR || t == SHORT) + exprType = INT; + } + + public void atMember(Member mem) throws CompileError { + atFieldRead(mem); + } + + public void atVariable(Variable v) throws CompileError { + Declarator d = v.getDeclarator(); + exprType = d.getType(); + arrayDim = d.getArrayDim(); + className = d.getClassName(); + } + + public void atKeyword(Keyword k) throws CompileError { + arrayDim = 0; + int token = k.get(); + switch (token) { + case TRUE : + case FALSE : + exprType = BOOLEAN; + break; + case NULL : + exprType = NULL; + break; + case THIS : + case SUPER : + exprType = CLASS; + if (token == THIS) + className = getThisName(); + else + className = getSuperName(); + break; + default : + fatal(); + } + } + + public void atStringL(StringL s) throws CompileError { + exprType = CLASS; + arrayDim = 0; + className = jvmJavaLangString; + } + + public void atIntConst(IntConst i) throws CompileError { + arrayDim = 0; + int type = i.getType(); + if (type == IntConstant || type == CharConstant) + exprType = (type == IntConstant ? INT : CHAR); + else + exprType = LONG; + } + + public void atDoubleConst(DoubleConst d) throws CompileError { + arrayDim = 0; + if (d.getType() == DoubleConstant) + exprType = DOUBLE; + else + exprType = FLOAT; + } +} diff --git a/src/main/javassist/compiler/ast/ASTList.java b/src/main/javassist/compiler/ast/ASTList.java new file mode 100644 index 0000000..b486668 --- /dev/null +++ b/src/main/javassist/compiler/ast/ASTList.java @@ -0,0 +1,159 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.compiler.ast; + +import javassist.compiler.CompileError; + +/** + * A linked list. + * The right subtree must be an ASTList object or null. + */ +public class ASTList extends ASTree { + private ASTree left; + private ASTList right; + + public ASTList(ASTree _head, ASTList _tail) { + left = _head; + right = _tail; + } + + public ASTList(ASTree _head) { + left = _head; + right = null; + } + + public static ASTList make(ASTree e1, ASTree e2, ASTree e3) { + return new ASTList(e1, new ASTList(e2, new ASTList(e3))); + } + + public ASTree getLeft() { return left; } + + public ASTree getRight() { return right; } + + public void setLeft(ASTree _left) { left = _left; } + + public void setRight(ASTree _right) { + right = (ASTList)_right; + } + + /** + * Returns the car part of the list. + */ + public ASTree head() { return left; } + + public void setHead(ASTree _head) { + left = _head; + } + + /** + * Returns the cdr part of the list. + */ + public ASTList tail() { return right; } + + public void setTail(ASTList _tail) { + right = _tail; + } + + public void accept(Visitor v) throws CompileError { v.atASTList(this); } + + public String toString() { + StringBuffer sbuf = new StringBuffer(); + sbuf.append("(<"); + sbuf.append(getTag()); + sbuf.append('>'); + ASTList list = this; + while (list != null) { + sbuf.append(' '); + ASTree a = list.left; + sbuf.append(a == null ? "<null>" : a.toString()); + list = list.right; + } + + sbuf.append(')'); + return sbuf.toString(); + } + + /** + * Returns the number of the elements in this list. + */ + public int length() { + return length(this); + } + + public static int length(ASTList list) { + if (list == null) + return 0; + + int n = 0; + while (list != null) { + list = list.right; + ++n; + } + + return n; + } + + /** + * Returns a sub list of the list. The sub list begins with the + * n-th element of the list. + * + * @param nth zero or more than zero. + */ + public ASTList sublist(int nth) { + ASTList list = this; + while (nth-- > 0) + list = list.right; + + return list; + } + + /** + * Substitutes <code>newObj</code> for <code>oldObj</code> in the + * list. + */ + public boolean subst(ASTree newObj, ASTree oldObj) { + for (ASTList list = this; list != null; list = list.right) + if (list.left == oldObj) { + list.left = newObj; + return true; + } + + return false; + } + + /** + * Appends an object to a list. + */ + public static ASTList append(ASTList a, ASTree b) { + return concat(a, new ASTList(b)); + } + + /** + * Concatenates two lists. + */ + public static ASTList concat(ASTList a, ASTList b) { + if (a == null) + return b; + else { + ASTList list = a; + while (list.right != null) + list = list.right; + + list.right = b; + return a; + } + } +} diff --git a/src/main/javassist/compiler/ast/ASTree.java b/src/main/javassist/compiler/ast/ASTree.java new file mode 100644 index 0000000..a0c4330 --- /dev/null +++ b/src/main/javassist/compiler/ast/ASTree.java @@ -0,0 +1,58 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.compiler.ast; + +import java.io.Serializable; +import javassist.compiler.CompileError; + +/** + * Abstract Syntax Tree. An ASTree object represents a node of + * a binary tree. If the node is a leaf node, both <code>getLeft()</code> + * and <code>getRight()</code> returns null. + */ +public abstract class ASTree implements Serializable { + public ASTree getLeft() { return null; } + + public ASTree getRight() { return null; } + + public void setLeft(ASTree _left) {} + + public void setRight(ASTree _right) {} + + /** + * Is a method for the visitor pattern. It calls + * <code>atXXX()</code> on the given visitor, where + * <code>XXX</code> is the class name of the node object. + */ + public abstract void accept(Visitor v) throws CompileError; + + public String toString() { + StringBuffer sbuf = new StringBuffer(); + sbuf.append('<'); + sbuf.append(getTag()); + sbuf.append('>'); + return sbuf.toString(); + } + + /** + * Returns the type of this node. This method is used by + * <code>toString()</code>. + */ + protected String getTag() { + String name = getClass().getName(); + return name.substring(name.lastIndexOf('.') + 1); + } +} diff --git a/src/main/javassist/compiler/ast/ArrayInit.java b/src/main/javassist/compiler/ast/ArrayInit.java new file mode 100644 index 0000000..a9c6c2f --- /dev/null +++ b/src/main/javassist/compiler/ast/ArrayInit.java @@ -0,0 +1,31 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.compiler.ast; + +import javassist.compiler.CompileError; + +/** + * Array initializer such as <code>{ 1, 2, 3 }</code>. + */ +public class ArrayInit extends ASTList { + public ArrayInit(ASTree firstElement) { + super(firstElement); + } + + public void accept(Visitor v) throws CompileError { v.atArrayInit(this); } + + public String getTag() { return "array"; } +} diff --git a/src/main/javassist/compiler/ast/AssignExpr.java b/src/main/javassist/compiler/ast/AssignExpr.java new file mode 100644 index 0000000..9e61447 --- /dev/null +++ b/src/main/javassist/compiler/ast/AssignExpr.java @@ -0,0 +1,40 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.compiler.ast; + +import javassist.compiler.CompileError; + +/** + * Assignment expression. + */ +public class AssignExpr extends Expr { + /* operator must be either of: + * =, %=, &=, *=, +=, -=, /=, ^=, |=, <<=, >>=, >>>= + */ + + private AssignExpr(int op, ASTree _head, ASTList _tail) { + super(op, _head, _tail); + } + + public static AssignExpr makeAssign(int op, ASTree oprand1, + ASTree oprand2) { + return new AssignExpr(op, oprand1, new ASTList(oprand2)); + } + + public void accept(Visitor v) throws CompileError { + v.atAssignExpr(this); + } +} diff --git a/src/main/javassist/compiler/ast/BinExpr.java b/src/main/javassist/compiler/ast/BinExpr.java new file mode 100644 index 0000000..1fb5a6a --- /dev/null +++ b/src/main/javassist/compiler/ast/BinExpr.java @@ -0,0 +1,41 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.compiler.ast; + +import javassist.compiler.CompileError; + +/** + * Binary expression. + * + * <p>If the operator is +, the right node might be null. + * See TypeChecker.atBinExpr(). + */ +public class BinExpr extends Expr { + /* operator must be either of: + * ||, &&, |, ^, &, ==, !=, <=, >=, <, >, + * <<, >>, >>>, +, -, *, /, % + */ + + private BinExpr(int op, ASTree _head, ASTList _tail) { + super(op, _head, _tail); + } + + public static BinExpr makeBin(int op, ASTree oprand1, ASTree oprand2) { + return new BinExpr(op, oprand1, new ASTList(oprand2)); + } + + public void accept(Visitor v) throws CompileError { v.atBinExpr(this); } +} diff --git a/src/main/javassist/compiler/ast/CallExpr.java b/src/main/javassist/compiler/ast/CallExpr.java new file mode 100644 index 0000000..544ce24 --- /dev/null +++ b/src/main/javassist/compiler/ast/CallExpr.java @@ -0,0 +1,46 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.compiler.ast; + +import javassist.compiler.CompileError; +import javassist.compiler.TokenId; +import javassist.compiler.MemberResolver; + +/** + * Method call expression. + */ +public class CallExpr extends Expr { + private MemberResolver.Method method; // cached result of lookupMethod() + + private CallExpr(ASTree _head, ASTList _tail) { + super(TokenId.CALL, _head, _tail); + method = null; + } + + public void setMethod(MemberResolver.Method m) { + method = m; + } + + public MemberResolver.Method getMethod() { + return method; + } + + public static CallExpr makeCall(ASTree target, ASTree args) { + return new CallExpr(target, new ASTList(args)); + } + + public void accept(Visitor v) throws CompileError { v.atCallExpr(this); } +} diff --git a/src/main/javassist/compiler/ast/CastExpr.java b/src/main/javassist/compiler/ast/CastExpr.java new file mode 100644 index 0000000..3fb5640 --- /dev/null +++ b/src/main/javassist/compiler/ast/CastExpr.java @@ -0,0 +1,55 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.compiler.ast; + +import javassist.compiler.TokenId; +import javassist.compiler.CompileError; + +/** + * Cast expression. + */ +public class CastExpr extends ASTList implements TokenId { + protected int castType; + protected int arrayDim; + + public CastExpr(ASTList className, int dim, ASTree expr) { + super(className, new ASTList(expr)); + castType = CLASS; + arrayDim = dim; + } + + public CastExpr(int type, int dim, ASTree expr) { + super(null, new ASTList(expr)); + castType = type; + arrayDim = dim; + } + + /* Returns CLASS, BOOLEAN, INT, or ... + */ + public int getType() { return castType; } + + public int getArrayDim() { return arrayDim; } + + public ASTList getClassName() { return (ASTList)getLeft(); } + + public ASTree getOprand() { return getRight().getLeft(); } + + public void setOprand(ASTree t) { getRight().setLeft(t); } + + public String getTag() { return "cast:" + castType + ":" + arrayDim; } + + public void accept(Visitor v) throws CompileError { v.atCastExpr(this); } +} diff --git a/src/main/javassist/compiler/ast/CondExpr.java b/src/main/javassist/compiler/ast/CondExpr.java new file mode 100644 index 0000000..2fb9603 --- /dev/null +++ b/src/main/javassist/compiler/ast/CondExpr.java @@ -0,0 +1,43 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.compiler.ast; + +import javassist.compiler.CompileError; + +/** + * Conditional expression. + */ +public class CondExpr extends ASTList { + public CondExpr(ASTree cond, ASTree thenp, ASTree elsep) { + super(cond, new ASTList(thenp, new ASTList(elsep))); + } + + public ASTree condExpr() { return head(); } + + public void setCond(ASTree t) { setHead(t); } + + public ASTree thenExpr() { return tail().head(); } + + public void setThen(ASTree t) { tail().setHead(t); } + + public ASTree elseExpr() { return tail().tail().head(); } + + public void setElse(ASTree t) { tail().tail().setHead(t); } + + public String getTag() { return "?:"; } + + public void accept(Visitor v) throws CompileError { v.atCondExpr(this); } +} diff --git a/src/main/javassist/compiler/ast/Declarator.java b/src/main/javassist/compiler/ast/Declarator.java new file mode 100644 index 0000000..d3a43f0 --- /dev/null +++ b/src/main/javassist/compiler/ast/Declarator.java @@ -0,0 +1,127 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.compiler.ast; + +import javassist.compiler.TokenId; +import javassist.compiler.CompileError; + +/** + * Variable declarator. + */ +public class Declarator extends ASTList implements TokenId { + protected int varType; + protected int arrayDim; + protected int localVar; + protected String qualifiedClass; // JVM-internal representation + + public Declarator(int type, int dim) { + super(null); + varType = type; + arrayDim = dim; + localVar = -1; + qualifiedClass = null; + } + + public Declarator(ASTList className, int dim) { + super(null); + varType = CLASS; + arrayDim = dim; + localVar = -1; + qualifiedClass = astToClassName(className, '/'); + } + + /* For declaring a pre-defined? local variable. + */ + public Declarator(int type, String jvmClassName, int dim, + int var, Symbol sym) { + super(null); + varType = type; + arrayDim = dim; + localVar = var; + qualifiedClass = jvmClassName; + setLeft(sym); + append(this, null); // initializer + } + + public Declarator make(Symbol sym, int dim, ASTree init) { + Declarator d = new Declarator(this.varType, this.arrayDim + dim); + d.qualifiedClass = this.qualifiedClass; + d.setLeft(sym); + append(d, init); + return d; + } + + /* Returns CLASS, BOOLEAN, BYTE, CHAR, SHORT, INT, LONG, FLOAT, + * or DOUBLE (or VOID) + */ + public int getType() { return varType; } + + public int getArrayDim() { return arrayDim; } + + public void addArrayDim(int d) { arrayDim += d; } + + public String getClassName() { return qualifiedClass; } + + public void setClassName(String s) { qualifiedClass = s; } + + public Symbol getVariable() { return (Symbol)getLeft(); } + + public void setVariable(Symbol sym) { setLeft(sym); } + + public ASTree getInitializer() { + ASTList t = tail(); + if (t != null) + return t.head(); + else + return null; + } + + public void setLocalVar(int n) { localVar = n; } + + public int getLocalVar() { return localVar; } + + public String getTag() { return "decl"; } + + public void accept(Visitor v) throws CompileError { + v.atDeclarator(this); + } + + public static String astToClassName(ASTList name, char sep) { + if (name == null) + return null; + + StringBuffer sbuf = new StringBuffer(); + astToClassName(sbuf, name, sep); + return sbuf.toString(); + } + + private static void astToClassName(StringBuffer sbuf, ASTList name, + char sep) { + for (;;) { + ASTree h = name.head(); + if (h instanceof Symbol) + sbuf.append(((Symbol)h).get()); + else if (h instanceof ASTList) + astToClassName(sbuf, (ASTList)h, sep); + + name = name.tail(); + if (name == null) + break; + + sbuf.append(sep); + } + } +} diff --git a/src/main/javassist/compiler/ast/DoubleConst.java b/src/main/javassist/compiler/ast/DoubleConst.java new file mode 100644 index 0000000..5276c2f --- /dev/null +++ b/src/main/javassist/compiler/ast/DoubleConst.java @@ -0,0 +1,94 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.compiler.ast; + +import javassist.compiler.CompileError; +import javassist.compiler.TokenId; + +/** + * Double constant. + */ +public class DoubleConst extends ASTree { + protected double value; + protected int type; + + public DoubleConst(double v, int tokenId) { value = v; type = tokenId; } + + public double get() { return value; } + + public void set(double v) { value = v; } + + /* Returns DoubleConstant or FloatConstant + */ + public int getType() { return type; } + + public String toString() { return Double.toString(value); } + + public void accept(Visitor v) throws CompileError { + v.atDoubleConst(this); + } + + public ASTree compute(int op, ASTree right) { + if (right instanceof IntConst) + return compute0(op, (IntConst)right); + else if (right instanceof DoubleConst) + return compute0(op, (DoubleConst)right); + else + return null; + } + + private DoubleConst compute0(int op, DoubleConst right) { + int newType; + if (this.type == TokenId.DoubleConstant + || right.type == TokenId.DoubleConstant) + newType = TokenId.DoubleConstant; + else + newType = TokenId.FloatConstant; + + return compute(op, this.value, right.value, newType); + } + + private DoubleConst compute0(int op, IntConst right) { + return compute(op, this.value, (double)right.value, this.type); + } + + private static DoubleConst compute(int op, double value1, double value2, + int newType) + { + double newValue; + switch (op) { + case '+' : + newValue = value1 + value2; + break; + case '-' : + newValue = value1 - value2; + break; + case '*' : + newValue = value1 * value2; + break; + case '/' : + newValue = value1 / value2; + break; + case '%' : + newValue = value1 % value2; + break; + default : + return null; + } + + return new DoubleConst(newValue, newType); + } +} diff --git a/src/main/javassist/compiler/ast/Expr.java b/src/main/javassist/compiler/ast/Expr.java new file mode 100644 index 0000000..ec11200 --- /dev/null +++ b/src/main/javassist/compiler/ast/Expr.java @@ -0,0 +1,84 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.compiler.ast; + +import javassist.compiler.TokenId; +import javassist.compiler.CompileError; + +/** + * Expression. + */ +public class Expr extends ASTList implements TokenId { + /* operator must be either of: + * (unary) +, (unary) -, ++, --, !, ~, + * ARRAY, . (dot), MEMBER (static member access). + * Otherwise, the object should be an instance of a subclass. + */ + + protected int operatorId; + + Expr(int op, ASTree _head, ASTList _tail) { + super(_head, _tail); + operatorId = op; + } + + Expr(int op, ASTree _head) { + super(_head); + operatorId = op; + } + + public static Expr make(int op, ASTree oprand1, ASTree oprand2) { + return new Expr(op, oprand1, new ASTList(oprand2)); + } + + public static Expr make(int op, ASTree oprand1) { + return new Expr(op, oprand1); + } + + public int getOperator() { return operatorId; } + + public void setOperator(int op) { operatorId = op; } + + public ASTree oprand1() { return getLeft(); } + + public void setOprand1(ASTree expr) { + setLeft(expr); + } + + public ASTree oprand2() { return getRight().getLeft(); } + + public void setOprand2(ASTree expr) { + getRight().setLeft(expr); + } + + public void accept(Visitor v) throws CompileError { v.atExpr(this); } + + public String getName() { + int id = operatorId; + if (id < 128) + return String.valueOf((char)id); + else if (NEQ <= id && id <= ARSHIFT_E) + return opNames[id - NEQ]; + else if (id == INSTANCEOF) + return "instanceof"; + else + return String.valueOf(id); + } + + protected String getTag() { + return "op:" + getName(); + } +} diff --git a/src/main/javassist/compiler/ast/FieldDecl.java b/src/main/javassist/compiler/ast/FieldDecl.java new file mode 100644 index 0000000..ce32b87 --- /dev/null +++ b/src/main/javassist/compiler/ast/FieldDecl.java @@ -0,0 +1,34 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.compiler.ast; + +import javassist.compiler.CompileError; + +public class FieldDecl extends ASTList { + public FieldDecl(ASTree _head, ASTList _tail) { + super(_head, _tail); + } + + public ASTList getModifiers() { return (ASTList)getLeft(); } + + public Declarator getDeclarator() { return (Declarator)tail().head(); } + + public ASTree getInit() { return (ASTree)sublist(2).head(); } + + public void accept(Visitor v) throws CompileError { + v.atFieldDecl(this); + } +} diff --git a/src/main/javassist/compiler/ast/InstanceOfExpr.java b/src/main/javassist/compiler/ast/InstanceOfExpr.java new file mode 100644 index 0000000..9813ce8 --- /dev/null +++ b/src/main/javassist/compiler/ast/InstanceOfExpr.java @@ -0,0 +1,39 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.compiler.ast; + +import javassist.compiler.CompileError; + +/** + * Instanceof expression. + */ +public class InstanceOfExpr extends CastExpr { + public InstanceOfExpr(ASTList className, int dim, ASTree expr) { + super(className, dim, expr); + } + + public InstanceOfExpr(int type, int dim, ASTree expr) { + super(type, dim, expr); + } + + public String getTag() { + return "instanceof:" + castType + ":" + arrayDim; + } + + public void accept(Visitor v) throws CompileError { + v.atInstanceOfExpr(this); + } +} diff --git a/src/main/javassist/compiler/ast/IntConst.java b/src/main/javassist/compiler/ast/IntConst.java new file mode 100644 index 0000000..703a0bf --- /dev/null +++ b/src/main/javassist/compiler/ast/IntConst.java @@ -0,0 +1,138 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.compiler.ast; + +import javassist.compiler.CompileError; +import javassist.compiler.TokenId; + +/** + * Integer constant. + */ +public class IntConst extends ASTree { + protected long value; + protected int type; + + public IntConst(long v, int tokenId) { value = v; type = tokenId; } + + public long get() { return value; } + + public void set(long v) { value = v; } + + /* Returns IntConstant, CharConstant, or LongConstant. + */ + public int getType() { return type; } + + public String toString() { return Long.toString(value); } + + public void accept(Visitor v) throws CompileError { + v.atIntConst(this); + } + + public ASTree compute(int op, ASTree right) { + if (right instanceof IntConst) + return compute0(op, (IntConst)right); + else if (right instanceof DoubleConst) + return compute0(op, (DoubleConst)right); + else + return null; + } + + private IntConst compute0(int op, IntConst right) { + int type1 = this.type; + int type2 = right.type; + int newType; + if (type1 == TokenId.LongConstant || type2 == TokenId.LongConstant) + newType = TokenId.LongConstant; + else if (type1 == TokenId.CharConstant + && type2 == TokenId.CharConstant) + newType = TokenId.CharConstant; + else + newType = TokenId.IntConstant; + + long value1 = this.value; + long value2 = right.value; + long newValue; + switch (op) { + case '+' : + newValue = value1 + value2; + break; + case '-' : + newValue = value1 - value2; + break; + case '*' : + newValue = value1 * value2; + break; + case '/' : + newValue = value1 / value2; + break; + case '%' : + newValue = value1 % value2; + break; + case '|' : + newValue = value1 | value2; + break; + case '^' : + newValue = value1 ^ value2; + break; + case '&' : + newValue = value1 & value2; + break; + case TokenId.LSHIFT : + newValue = value << (int)value2; + newType = type1; + break; + case TokenId.RSHIFT : + newValue = value >> (int)value2; + newType = type1; + break; + case TokenId.ARSHIFT : + newValue = value >>> (int)value2; + newType = type1; + break; + default : + return null; + } + + return new IntConst(newValue, newType); + } + + private DoubleConst compute0(int op, DoubleConst right) { + double value1 = (double)this.value; + double value2 = right.value; + double newValue; + switch (op) { + case '+' : + newValue = value1 + value2; + break; + case '-' : + newValue = value1 - value2; + break; + case '*' : + newValue = value1 * value2; + break; + case '/' : + newValue = value1 / value2; + break; + case '%' : + newValue = value1 % value2; + break; + default : + return null; + } + + return new DoubleConst(newValue, right.type); + } +} diff --git a/src/main/javassist/compiler/ast/Keyword.java b/src/main/javassist/compiler/ast/Keyword.java new file mode 100644 index 0000000..a1a9ebf --- /dev/null +++ b/src/main/javassist/compiler/ast/Keyword.java @@ -0,0 +1,35 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.compiler.ast; + +import javassist.compiler.CompileError; + +/** + * Keyword. + */ +public class Keyword extends ASTree { + protected int tokenId; + + public Keyword(int token) { + tokenId = token; + } + + public int get() { return tokenId; } + + public String toString() { return "id:" + tokenId; } + + public void accept(Visitor v) throws CompileError { v.atKeyword(this); } +} diff --git a/src/main/javassist/compiler/ast/Member.java b/src/main/javassist/compiler/ast/Member.java new file mode 100644 index 0000000..404f2b8 --- /dev/null +++ b/src/main/javassist/compiler/ast/Member.java @@ -0,0 +1,39 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.compiler.ast; + +import javassist.compiler.CompileError; +import javassist.CtField; + +/** + * Member name. + */ +public class Member extends Symbol { + // cache maintained by fieldAccess() in TypeChecker. + // this is used to obtain the value of a static final field. + private CtField field; + + public Member(String name) { + super(name); + field = null; + } + + public void setField(CtField f) { field = f; } + + public CtField getField() { return field; } + + public void accept(Visitor v) throws CompileError { v.atMember(this); } +} diff --git a/src/main/javassist/compiler/ast/MethodDecl.java b/src/main/javassist/compiler/ast/MethodDecl.java new file mode 100644 index 0000000..8d0661c --- /dev/null +++ b/src/main/javassist/compiler/ast/MethodDecl.java @@ -0,0 +1,45 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.compiler.ast; + +import javassist.compiler.CompileError; + +public class MethodDecl extends ASTList { + public static final String initName = "<init>"; + + public MethodDecl(ASTree _head, ASTList _tail) { + super(_head, _tail); + } + + public boolean isConstructor() { + Symbol sym = getReturn().getVariable(); + return sym != null && initName.equals(sym.get()); + } + + public ASTList getModifiers() { return (ASTList)getLeft(); } + + public Declarator getReturn() { return (Declarator)tail().head(); } + + public ASTList getParams() { return (ASTList)sublist(2).head(); } + + public ASTList getThrows() { return (ASTList)sublist(3).head(); } + + public Stmnt getBody() { return (Stmnt)sublist(4).head(); } + + public void accept(Visitor v) throws CompileError { + v.atMethodDecl(this); + } +} diff --git a/src/main/javassist/compiler/ast/NewExpr.java b/src/main/javassist/compiler/ast/NewExpr.java new file mode 100644 index 0000000..db3cb51 --- /dev/null +++ b/src/main/javassist/compiler/ast/NewExpr.java @@ -0,0 +1,77 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.compiler.ast; + +import javassist.compiler.TokenId; +import javassist.compiler.CompileError; + +/** + * New Expression. + */ +public class NewExpr extends ASTList implements TokenId { + protected boolean newArray; + protected int arrayType; + + public NewExpr(ASTList className, ASTList args) { + super(className, new ASTList(args)); + newArray = false; + arrayType = CLASS; + } + + public NewExpr(int type, ASTList arraySize, ArrayInit init) { + super(null, new ASTList(arraySize)); + newArray = true; + arrayType = type; + if (init != null) + append(this, init); + } + + public static NewExpr makeObjectArray(ASTList className, + ASTList arraySize, ArrayInit init) { + NewExpr e = new NewExpr(className, arraySize); + e.newArray = true; + if (init != null) + append(e, init); + + return e; + } + + public boolean isArray() { return newArray; } + + /* TokenId.CLASS, TokenId.INT, ... + */ + public int getArrayType() { return arrayType; } + + public ASTList getClassName() { return (ASTList)getLeft(); } + + public ASTList getArguments() { return (ASTList)getRight().getLeft(); } + + public ASTList getArraySize() { return getArguments(); } + + public ArrayInit getInitializer() { + ASTree t = getRight().getRight(); + if (t == null) + return null; + else + return (ArrayInit)t.getLeft(); + } + + public void accept(Visitor v) throws CompileError { v.atNewExpr(this); } + + protected String getTag() { + return newArray ? "new[]" : "new"; + } +} diff --git a/src/main/javassist/compiler/ast/Pair.java b/src/main/javassist/compiler/ast/Pair.java new file mode 100644 index 0000000..1831a35 --- /dev/null +++ b/src/main/javassist/compiler/ast/Pair.java @@ -0,0 +1,51 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.compiler.ast; + +import javassist.compiler.CompileError; + +/** + * A node of a a binary tree. This class provides concrete methods + * overriding abstract methods in ASTree. + */ +public class Pair extends ASTree { + protected ASTree left, right; + + public Pair(ASTree _left, ASTree _right) { + left = _left; + right = _right; + } + + public void accept(Visitor v) throws CompileError { v.atPair(this); } + + public String toString() { + StringBuffer sbuf = new StringBuffer(); + sbuf.append("(<Pair> "); + sbuf.append(left == null ? "<null>" : left.toString()); + sbuf.append(" . "); + sbuf.append(right == null ? "<null>" : right.toString()); + sbuf.append(')'); + return sbuf.toString(); + } + + public ASTree getLeft() { return left; } + + public ASTree getRight() { return right; } + + public void setLeft(ASTree _left) { left = _left; } + + public void setRight(ASTree _right) { right = _right; } +} diff --git a/src/main/javassist/compiler/ast/Stmnt.java b/src/main/javassist/compiler/ast/Stmnt.java new file mode 100644 index 0000000..d5c6d62 --- /dev/null +++ b/src/main/javassist/compiler/ast/Stmnt.java @@ -0,0 +1,59 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.compiler.ast; + +import javassist.compiler.TokenId; +import javassist.compiler.CompileError; + +/** + * Statement. + */ +public class Stmnt extends ASTList implements TokenId { + protected int operatorId; + + public Stmnt(int op, ASTree _head, ASTList _tail) { + super(_head, _tail); + operatorId = op; + } + + public Stmnt(int op, ASTree _head) { + super(_head); + operatorId = op; + } + + public Stmnt(int op) { + this(op, null); + } + + public static Stmnt make(int op, ASTree oprand1, ASTree oprand2) { + return new Stmnt(op, oprand1, new ASTList(oprand2)); + } + + public static Stmnt make(int op, ASTree op1, ASTree op2, ASTree op3) { + return new Stmnt(op, op1, new ASTList(op2, new ASTList(op3))); + } + + public void accept(Visitor v) throws CompileError { v.atStmnt(this); } + + public int getOperator() { return operatorId; } + + protected String getTag() { + if (operatorId < 128) + return "stmnt:" + (char)operatorId; + else + return "stmnt:" + operatorId; + } +} diff --git a/src/main/javassist/compiler/ast/StringL.java b/src/main/javassist/compiler/ast/StringL.java new file mode 100644 index 0000000..b9e5fe8 --- /dev/null +++ b/src/main/javassist/compiler/ast/StringL.java @@ -0,0 +1,35 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.compiler.ast; + +import javassist.compiler.CompileError; + +/** + * String literal. + */ +public class StringL extends ASTree { + protected String text; + + public StringL(String t) { + text = t; + } + + public String get() { return text; } + + public String toString() { return "\"" + text + "\""; } + + public void accept(Visitor v) throws CompileError { v.atStringL(this); } +} diff --git a/src/main/javassist/compiler/ast/Symbol.java b/src/main/javassist/compiler/ast/Symbol.java new file mode 100644 index 0000000..0b26e75 --- /dev/null +++ b/src/main/javassist/compiler/ast/Symbol.java @@ -0,0 +1,35 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.compiler.ast; + +import javassist.compiler.CompileError; + +/** + * Identifier. + */ +public class Symbol extends ASTree { + protected String identifier; + + public Symbol(String sym) { + identifier = sym; + } + + public String get() { return identifier; } + + public String toString() { return identifier; } + + public void accept(Visitor v) throws CompileError { v.atSymbol(this); } +} diff --git a/src/main/javassist/compiler/ast/Variable.java b/src/main/javassist/compiler/ast/Variable.java new file mode 100644 index 0000000..353690e --- /dev/null +++ b/src/main/javassist/compiler/ast/Variable.java @@ -0,0 +1,38 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.compiler.ast; + +import javassist.compiler.CompileError; + +/** + * Variable. + */ +public class Variable extends Symbol { + protected Declarator declarator; + + public Variable(String sym, Declarator d) { + super(sym); + declarator = d; + } + + public Declarator getDeclarator() { return declarator; } + + public String toString() { + return identifier + ":" + declarator.getType(); + } + + public void accept(Visitor v) throws CompileError { v.atVariable(this); } +} diff --git a/src/main/javassist/compiler/ast/Visitor.java b/src/main/javassist/compiler/ast/Visitor.java new file mode 100644 index 0000000..b4cbf34 --- /dev/null +++ b/src/main/javassist/compiler/ast/Visitor.java @@ -0,0 +1,51 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.compiler.ast; + +import javassist.compiler.CompileError; + +/** + * The visitor pattern. + * + * @see ast.ASTree#accept(Visitor) + */ +public class Visitor { + public void atASTList(ASTList n) throws CompileError {} + public void atPair(Pair n) throws CompileError {} + + public void atFieldDecl(FieldDecl n) throws CompileError {} + public void atMethodDecl(MethodDecl n) throws CompileError {} + public void atStmnt(Stmnt n) throws CompileError {} + public void atDeclarator(Declarator n) throws CompileError {} + + public void atAssignExpr(AssignExpr n) throws CompileError {} + public void atCondExpr(CondExpr n) throws CompileError {} + public void atBinExpr(BinExpr n) throws CompileError {} + public void atExpr(Expr n) throws CompileError {} + public void atCallExpr(CallExpr n) throws CompileError {} + public void atCastExpr(CastExpr n) throws CompileError {} + public void atInstanceOfExpr(InstanceOfExpr n) throws CompileError {} + public void atNewExpr(NewExpr n) throws CompileError {} + + public void atSymbol(Symbol n) throws CompileError {} + public void atMember(Member n) throws CompileError {} + public void atVariable(Variable n) throws CompileError {} + public void atKeyword(Keyword n) throws CompileError {} + public void atStringL(StringL n) throws CompileError {} + public void atIntConst(IntConst n) throws CompileError {} + public void atDoubleConst(DoubleConst n) throws CompileError {} + public void atArrayInit(ArrayInit n) throws CompileError {} +} diff --git a/src/main/javassist/convert/TransformAccessArrayField.java b/src/main/javassist/convert/TransformAccessArrayField.java new file mode 100644 index 0000000..f70148f --- /dev/null +++ b/src/main/javassist/convert/TransformAccessArrayField.java @@ -0,0 +1,269 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ +package javassist.convert; + +import javassist.CannotCompileException; +import javassist.ClassPool; +import javassist.CtClass; +import javassist.NotFoundException; +import javassist.CodeConverter.ArrayAccessReplacementMethodNames; +import javassist.bytecode.BadBytecode; +import javassist.bytecode.CodeIterator; +import javassist.bytecode.ConstPool; +import javassist.bytecode.Descriptor; +import javassist.bytecode.MethodInfo; +import javassist.bytecode.analysis.Analyzer; +import javassist.bytecode.analysis.Frame; + +/** + * A transformer which replaces array access with static method invocations. + * + * @author <a href="kabir.khan@jboss.com">Kabir Khan</a> + * @author Jason T. Greene + * @version $Revision: 1.8 $ + */ +public final class TransformAccessArrayField extends Transformer { + private final String methodClassname; + private final ArrayAccessReplacementMethodNames names; + private Frame[] frames; + private int offset; + + public TransformAccessArrayField(Transformer next, String methodClassname, + ArrayAccessReplacementMethodNames names) throws NotFoundException { + super(next); + this.methodClassname = methodClassname; + this.names = names; + + } + + public void initialize(ConstPool cp, CtClass clazz, MethodInfo minfo) throws CannotCompileException { + /* + * This transformer must be isolated from other transformers, since some + * of them affect the local variable and stack maximums without updating + * the code attribute to reflect the changes. This screws up the + * data-flow analyzer, since it relies on consistent code state. Even + * if the attribute values were updated correctly, we would have to + * detect it, and redo analysis, which is not cheap. Instead, we are + * better off doing all changes in initialize() before everyone else has + * a chance to muck things up. + */ + CodeIterator iterator = minfo.getCodeAttribute().iterator(); + while (iterator.hasNext()) { + try { + int pos = iterator.next(); + int c = iterator.byteAt(pos); + + if (c == AALOAD) + initFrames(clazz, minfo); + + if (c == AALOAD || c == BALOAD || c == CALOAD || c == DALOAD + || c == FALOAD || c == IALOAD || c == LALOAD + || c == SALOAD) { + pos = replace(cp, iterator, pos, c, getLoadReplacementSignature(c)); + } else if (c == AASTORE || c == BASTORE || c == CASTORE + || c == DASTORE || c == FASTORE || c == IASTORE + || c == LASTORE || c == SASTORE) { + pos = replace(cp, iterator, pos, c, getStoreReplacementSignature(c)); + } + + } catch (Exception e) { + throw new CannotCompileException(e); + } + } + } + + public void clean() { + frames = null; + offset = -1; + } + + public int transform(CtClass tclazz, int pos, CodeIterator iterator, + ConstPool cp) throws BadBytecode { + // Do nothing, see above comment + return pos; + } + + private Frame getFrame(int pos) throws BadBytecode { + return frames[pos - offset]; // Adjust pos + } + + private void initFrames(CtClass clazz, MethodInfo minfo) throws BadBytecode { + if (frames == null) { + frames = ((new Analyzer())).analyze(clazz, minfo); + offset = 0; // start tracking changes + } + } + + private int updatePos(int pos, int increment) { + if (offset > -1) + offset += increment; + + return pos + increment; + } + + private String getTopType(int pos) throws BadBytecode { + Frame frame = getFrame(pos); + if (frame == null) + return null; + + CtClass clazz = frame.peek().getCtClass(); + return clazz != null ? Descriptor.toJvmName(clazz) : null; + } + + private int replace(ConstPool cp, CodeIterator iterator, int pos, + int opcode, String signature) throws BadBytecode { + String castType = null; + String methodName = getMethodName(opcode); + if (methodName != null) { + // See if the object must be cast + if (opcode == AALOAD) { + castType = getTopType(iterator.lookAhead()); + // Do not replace an AALOAD instruction that we do not have a type for + // This happens when the state is guaranteed to be null (Type.UNINIT) + // So we don't really care about this case. + if (castType == null) + return pos; + if ("java/lang/Object".equals(castType)) + castType = null; + } + + // The gap may include extra padding + // Write a nop in case the padding pushes the instruction forward + iterator.writeByte(NOP, pos); + CodeIterator.Gap gap + = iterator.insertGapAt(pos, castType != null ? 5 : 2, false); + pos = gap.position; + int mi = cp.addClassInfo(methodClassname); + int methodref = cp.addMethodrefInfo(mi, methodName, signature); + iterator.writeByte(INVOKESTATIC, pos); + iterator.write16bit(methodref, pos + 1); + + if (castType != null) { + int index = cp.addClassInfo(castType); + iterator.writeByte(CHECKCAST, pos + 3); + iterator.write16bit(index, pos + 4); + } + + pos = updatePos(pos, gap.length); + } + + return pos; + } + + private String getMethodName(int opcode) { + String methodName = null; + switch (opcode) { + case AALOAD: + methodName = names.objectRead(); + break; + case BALOAD: + methodName = names.byteOrBooleanRead(); + break; + case CALOAD: + methodName = names.charRead(); + break; + case DALOAD: + methodName = names.doubleRead(); + break; + case FALOAD: + methodName = names.floatRead(); + break; + case IALOAD: + methodName = names.intRead(); + break; + case SALOAD: + methodName = names.shortRead(); + break; + case LALOAD: + methodName = names.longRead(); + break; + case AASTORE: + methodName = names.objectWrite(); + break; + case BASTORE: + methodName = names.byteOrBooleanWrite(); + break; + case CASTORE: + methodName = names.charWrite(); + break; + case DASTORE: + methodName = names.doubleWrite(); + break; + case FASTORE: + methodName = names.floatWrite(); + break; + case IASTORE: + methodName = names.intWrite(); + break; + case SASTORE: + methodName = names.shortWrite(); + break; + case LASTORE: + methodName = names.longWrite(); + break; + } + + if (methodName.equals("")) + methodName = null; + + return methodName; + } + + private String getLoadReplacementSignature(int opcode) throws BadBytecode { + switch (opcode) { + case AALOAD: + return "(Ljava/lang/Object;I)Ljava/lang/Object;"; + case BALOAD: + return "(Ljava/lang/Object;I)B"; + case CALOAD: + return "(Ljava/lang/Object;I)C"; + case DALOAD: + return "(Ljava/lang/Object;I)D"; + case FALOAD: + return "(Ljava/lang/Object;I)F"; + case IALOAD: + return "(Ljava/lang/Object;I)I"; + case SALOAD: + return "(Ljava/lang/Object;I)S"; + case LALOAD: + return "(Ljava/lang/Object;I)J"; + } + + throw new BadBytecode(opcode); + } + + private String getStoreReplacementSignature(int opcode) throws BadBytecode { + switch (opcode) { + case AASTORE: + return "(Ljava/lang/Object;ILjava/lang/Object;)V"; + case BASTORE: + return "(Ljava/lang/Object;IB)V"; + case CASTORE: + return "(Ljava/lang/Object;IC)V"; + case DASTORE: + return "(Ljava/lang/Object;ID)V"; + case FASTORE: + return "(Ljava/lang/Object;IF)V"; + case IASTORE: + return "(Ljava/lang/Object;II)V"; + case SASTORE: + return "(Ljava/lang/Object;IS)V"; + case LASTORE: + return "(Ljava/lang/Object;IJ)V"; + } + + throw new BadBytecode(opcode); + } +} diff --git a/src/main/javassist/convert/TransformAfter.java b/src/main/javassist/convert/TransformAfter.java new file mode 100644 index 0000000..6015115 --- /dev/null +++ b/src/main/javassist/convert/TransformAfter.java @@ -0,0 +1,46 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.convert; + +import javassist.CtMethod; +import javassist.NotFoundException; +import javassist.bytecode.*; + +public class TransformAfter extends TransformBefore { + public TransformAfter(Transformer next, + CtMethod origMethod, CtMethod afterMethod) + throws NotFoundException + { + super(next, origMethod, afterMethod); + } + + protected int match2(int pos, CodeIterator iterator) throws BadBytecode { + iterator.move(pos); + iterator.insert(saveCode); + iterator.insert(loadCode); + int p = iterator.insertGap(3); + iterator.setMark(p); + iterator.insert(loadCode); + pos = iterator.next(); + p = iterator.getMark(); + iterator.writeByte(iterator.byteAt(pos), p); + iterator.write16bit(iterator.u16bitAt(pos + 1), p + 1); + iterator.writeByte(INVOKESTATIC, pos); + iterator.write16bit(newIndex, pos + 1); + iterator.move(p); + return iterator.next(); + } +} diff --git a/src/main/javassist/convert/TransformBefore.java b/src/main/javassist/convert/TransformBefore.java new file mode 100644 index 0000000..2ad585f --- /dev/null +++ b/src/main/javassist/convert/TransformBefore.java @@ -0,0 +1,107 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.convert; + +import javassist.CtClass; +import javassist.CtMethod; +import javassist.NotFoundException; +import javassist.bytecode.*; + +public class TransformBefore extends TransformCall { + protected CtClass[] parameterTypes; + protected int locals; + protected int maxLocals; + protected byte[] saveCode, loadCode; + + public TransformBefore(Transformer next, + CtMethod origMethod, CtMethod beforeMethod) + throws NotFoundException + { + super(next, origMethod, beforeMethod); + + // override + methodDescriptor = origMethod.getMethodInfo2().getDescriptor(); + + parameterTypes = origMethod.getParameterTypes(); + locals = 0; + maxLocals = 0; + saveCode = loadCode = null; + } + + public void initialize(ConstPool cp, CodeAttribute attr) { + super.initialize(cp, attr); + locals = 0; + maxLocals = attr.getMaxLocals(); + saveCode = loadCode = null; + } + + protected int match(int c, int pos, CodeIterator iterator, + int typedesc, ConstPool cp) throws BadBytecode + { + if (newIndex == 0) { + String desc = Descriptor.ofParameters(parameterTypes) + 'V'; + desc = Descriptor.insertParameter(classname, desc); + int nt = cp.addNameAndTypeInfo(newMethodname, desc); + int ci = cp.addClassInfo(newClassname); + newIndex = cp.addMethodrefInfo(ci, nt); + constPool = cp; + } + + if (saveCode == null) + makeCode(parameterTypes, cp); + + return match2(pos, iterator); + } + + protected int match2(int pos, CodeIterator iterator) throws BadBytecode { + iterator.move(pos); + iterator.insert(saveCode); + iterator.insert(loadCode); + int p = iterator.insertGap(3); + iterator.writeByte(INVOKESTATIC, p); + iterator.write16bit(newIndex, p + 1); + iterator.insert(loadCode); + return iterator.next(); + } + + public int extraLocals() { return locals; } + + protected void makeCode(CtClass[] paramTypes, ConstPool cp) { + Bytecode save = new Bytecode(cp, 0, 0); + Bytecode load = new Bytecode(cp, 0, 0); + + int var = maxLocals; + int len = (paramTypes == null) ? 0 : paramTypes.length; + load.addAload(var); + makeCode2(save, load, 0, len, paramTypes, var + 1); + save.addAstore(var); + + saveCode = save.get(); + loadCode = load.get(); + } + + private void makeCode2(Bytecode save, Bytecode load, + int i, int n, CtClass[] paramTypes, int var) + { + if (i < n) { + int size = load.addLoad(var, paramTypes[i]); + makeCode2(save, load, i + 1, n, paramTypes, var + size); + save.addStore(var, paramTypes[i]); + } + else + locals = var - maxLocals; + } +} diff --git a/src/main/javassist/convert/TransformCall.java b/src/main/javassist/convert/TransformCall.java new file mode 100644 index 0000000..fb8395e --- /dev/null +++ b/src/main/javassist/convert/TransformCall.java @@ -0,0 +1,129 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.convert; + +import javassist.CtClass; +import javassist.CtMethod; +import javassist.ClassPool; +import javassist.Modifier; +import javassist.NotFoundException; +import javassist.bytecode.*; + +public class TransformCall extends Transformer { + protected String classname, methodname, methodDescriptor; + protected String newClassname, newMethodname; + protected boolean newMethodIsPrivate; + + /* cache */ + protected int newIndex; + protected ConstPool constPool; + + public TransformCall(Transformer next, CtMethod origMethod, + CtMethod substMethod) + { + this(next, origMethod.getName(), substMethod); + classname = origMethod.getDeclaringClass().getName(); + } + + public TransformCall(Transformer next, String oldMethodName, + CtMethod substMethod) + { + super(next); + methodname = oldMethodName; + methodDescriptor = substMethod.getMethodInfo2().getDescriptor(); + classname = newClassname = substMethod.getDeclaringClass().getName(); + newMethodname = substMethod.getName(); + constPool = null; + newMethodIsPrivate = Modifier.isPrivate(substMethod.getModifiers()); + } + + public void initialize(ConstPool cp, CodeAttribute attr) { + if (constPool != cp) + newIndex = 0; + } + + /** + * Modify INVOKEINTERFACE, INVOKESPECIAL, INVOKESTATIC and INVOKEVIRTUAL + * so that a different method is invoked. The class name in the operand + * of these instructions might be a subclass of the target class specified + * by <code>classname</code>. This method transforms the instruction + * in that case unless the subclass overrides the target method. + */ + public int transform(CtClass clazz, int pos, CodeIterator iterator, + ConstPool cp) throws BadBytecode + { + int c = iterator.byteAt(pos); + if (c == INVOKEINTERFACE || c == INVOKESPECIAL + || c == INVOKESTATIC || c == INVOKEVIRTUAL) { + int index = iterator.u16bitAt(pos + 1); + String cname = cp.eqMember(methodname, methodDescriptor, index); + if (cname != null && matchClass(cname, clazz.getClassPool())) { + int ntinfo = cp.getMemberNameAndType(index); + pos = match(c, pos, iterator, + cp.getNameAndTypeDescriptor(ntinfo), cp); + } + } + + return pos; + } + + private boolean matchClass(String name, ClassPool pool) { + if (classname.equals(name)) + return true; + + try { + CtClass clazz = pool.get(name); + CtClass declClazz = pool.get(classname); + if (clazz.subtypeOf(declClazz)) + try { + CtMethod m = clazz.getMethod(methodname, methodDescriptor); + return m.getDeclaringClass().getName().equals(classname); + } + catch (NotFoundException e) { + // maybe the original method has been removed. + return true; + } + } + catch (NotFoundException e) { + return false; + } + + return false; + } + + protected int match(int c, int pos, CodeIterator iterator, + int typedesc, ConstPool cp) throws BadBytecode + { + if (newIndex == 0) { + int nt = cp.addNameAndTypeInfo(cp.addUtf8Info(newMethodname), + typedesc); + int ci = cp.addClassInfo(newClassname); + if (c == INVOKEINTERFACE) + newIndex = cp.addInterfaceMethodrefInfo(ci, nt); + else { + if (newMethodIsPrivate && c == INVOKEVIRTUAL) + iterator.writeByte(INVOKESPECIAL, pos); + + newIndex = cp.addMethodrefInfo(ci, nt); + } + + constPool = cp; + } + + iterator.write16bit(newIndex, pos + 1); + return pos; + } +} diff --git a/src/main/javassist/convert/TransformFieldAccess.java b/src/main/javassist/convert/TransformFieldAccess.java new file mode 100644 index 0000000..d97c95d --- /dev/null +++ b/src/main/javassist/convert/TransformFieldAccess.java @@ -0,0 +1,81 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.convert; + +import javassist.bytecode.*; +import javassist.CtClass; +import javassist.CtField; +import javassist.Modifier; + +final public class TransformFieldAccess extends Transformer { + private String newClassname, newFieldname; + private String fieldname; + private CtClass fieldClass; + private boolean isPrivate; + + /* cache */ + private int newIndex; + private ConstPool constPool; + + public TransformFieldAccess(Transformer next, CtField field, + String newClassname, String newFieldname) + { + super(next); + this.fieldClass = field.getDeclaringClass(); + this.fieldname = field.getName(); + this.isPrivate = Modifier.isPrivate(field.getModifiers()); + this.newClassname = newClassname; + this.newFieldname = newFieldname; + this.constPool = null; + } + + public void initialize(ConstPool cp, CodeAttribute attr) { + if (constPool != cp) + newIndex = 0; + } + + /** + * Modify GETFIELD, GETSTATIC, PUTFIELD, and PUTSTATIC so that + * a different field is accessed. The new field must be declared + * in a superclass of the class in which the original field is + * declared. + */ + public int transform(CtClass clazz, int pos, + CodeIterator iterator, ConstPool cp) + { + int c = iterator.byteAt(pos); + if (c == GETFIELD || c == GETSTATIC + || c == PUTFIELD || c == PUTSTATIC) { + int index = iterator.u16bitAt(pos + 1); + String typedesc + = TransformReadField.isField(clazz.getClassPool(), cp, + fieldClass, fieldname, isPrivate, index); + if (typedesc != null) { + if (newIndex == 0) { + int nt = cp.addNameAndTypeInfo(newFieldname, + typedesc); + newIndex = cp.addFieldrefInfo( + cp.addClassInfo(newClassname), nt); + constPool = cp; + } + + iterator.write16bit(newIndex, pos + 1); + } + } + + return pos; + } +} diff --git a/src/main/javassist/convert/TransformNew.java b/src/main/javassist/convert/TransformNew.java new file mode 100644 index 0000000..f796489 --- /dev/null +++ b/src/main/javassist/convert/TransformNew.java @@ -0,0 +1,102 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.convert; + +import javassist.bytecode.*; +import javassist.CtClass; +import javassist.CannotCompileException; + +final public class TransformNew extends Transformer { + private int nested; + private String classname, trapClass, trapMethod; + + public TransformNew(Transformer next, + String classname, String trapClass, String trapMethod) { + super(next); + this.classname = classname; + this.trapClass = trapClass; + this.trapMethod = trapMethod; + } + + public void initialize(ConstPool cp, CodeAttribute attr) { + nested = 0; + } + + /** + * Replace a sequence of + * NEW classname + * DUP + * ... + * INVOKESPECIAL + * with + * NOP + * NOP + * ... + * INVOKESTATIC trapMethod in trapClass + */ + public int transform(CtClass clazz, int pos, CodeIterator iterator, + ConstPool cp) throws CannotCompileException + { + int index; + int c = iterator.byteAt(pos); + if (c == NEW) { + index = iterator.u16bitAt(pos + 1); + if (cp.getClassInfo(index).equals(classname)) { + if (iterator.byteAt(pos + 3) != DUP) + throw new CannotCompileException( + "NEW followed by no DUP was found"); + + iterator.writeByte(NOP, pos); + iterator.writeByte(NOP, pos + 1); + iterator.writeByte(NOP, pos + 2); + iterator.writeByte(NOP, pos + 3); + ++nested; + + StackMapTable smt + = (StackMapTable)iterator.get().getAttribute(StackMapTable.tag); + if (smt != null) + smt.removeNew(pos); + + StackMap sm + = (StackMap)iterator.get().getAttribute(StackMap.tag); + if (sm != null) + sm.removeNew(pos); + } + } + else if (c == INVOKESPECIAL) { + index = iterator.u16bitAt(pos + 1); + int typedesc = cp.isConstructor(classname, index); + if (typedesc != 0 && nested > 0) { + int methodref = computeMethodref(typedesc, cp); + iterator.writeByte(INVOKESTATIC, pos); + iterator.write16bit(methodref, pos + 1); + --nested; + } + } + + return pos; + } + + private int computeMethodref(int typedesc, ConstPool cp) { + int classIndex = cp.addClassInfo(trapClass); + int mnameIndex = cp.addUtf8Info(trapMethod); + typedesc = cp.addUtf8Info( + Descriptor.changeReturnType(classname, + cp.getUtf8Info(typedesc))); + return cp.addMethodrefInfo(classIndex, + cp.addNameAndTypeInfo(mnameIndex, typedesc)); + } +} diff --git a/src/main/javassist/convert/TransformNewClass.java b/src/main/javassist/convert/TransformNewClass.java new file mode 100644 index 0000000..f34ef83 --- /dev/null +++ b/src/main/javassist/convert/TransformNewClass.java @@ -0,0 +1,82 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.convert; + +import javassist.bytecode.*; +import javassist.CtClass; +import javassist.CannotCompileException; + +final public class TransformNewClass extends Transformer { + private int nested; + private String classname, newClassName; + private int newClassIndex, newMethodNTIndex, newMethodIndex; + + public TransformNewClass(Transformer next, + String classname, String newClassName) { + super(next); + this.classname = classname; + this.newClassName = newClassName; + } + + public void initialize(ConstPool cp, CodeAttribute attr) { + nested = 0; + newClassIndex = newMethodNTIndex = newMethodIndex = 0; + } + + /** + * Modifies a sequence of + * NEW classname + * DUP + * ... + * INVOKESPECIAL classname:method + */ + public int transform(CtClass clazz, int pos, CodeIterator iterator, + ConstPool cp) throws CannotCompileException + { + int index; + int c = iterator.byteAt(pos); + if (c == NEW) { + index = iterator.u16bitAt(pos + 1); + if (cp.getClassInfo(index).equals(classname)) { + if (iterator.byteAt(pos + 3) != DUP) + throw new CannotCompileException( + "NEW followed by no DUP was found"); + + if (newClassIndex == 0) + newClassIndex = cp.addClassInfo(newClassName); + + iterator.write16bit(newClassIndex, pos + 1); + ++nested; + } + } + else if (c == INVOKESPECIAL) { + index = iterator.u16bitAt(pos + 1); + int typedesc = cp.isConstructor(classname, index); + if (typedesc != 0 && nested > 0) { + int nt = cp.getMethodrefNameAndType(index); + if (newMethodNTIndex != nt) { + newMethodNTIndex = nt; + newMethodIndex = cp.addMethodrefInfo(newClassIndex, nt); + } + + iterator.write16bit(newMethodIndex, pos + 1); + --nested; + } + } + + return pos; + } +} diff --git a/src/main/javassist/convert/TransformReadField.java b/src/main/javassist/convert/TransformReadField.java new file mode 100644 index 0000000..a9e613c --- /dev/null +++ b/src/main/javassist/convert/TransformReadField.java @@ -0,0 +1,95 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.convert; + +import javassist.bytecode.*; +import javassist.ClassPool; +import javassist.CtClass; +import javassist.CtField; +import javassist.NotFoundException; +import javassist.Modifier; + +public class TransformReadField extends Transformer { + protected String fieldname; + protected CtClass fieldClass; + protected boolean isPrivate; + protected String methodClassname, methodName; + + public TransformReadField(Transformer next, CtField field, + String methodClassname, String methodName) + { + super(next); + this.fieldClass = field.getDeclaringClass(); + this.fieldname = field.getName(); + this.methodClassname = methodClassname; + this.methodName = methodName; + this.isPrivate = Modifier.isPrivate(field.getModifiers()); + } + + static String isField(ClassPool pool, ConstPool cp, CtClass fclass, + String fname, boolean is_private, int index) { + if (!cp.getFieldrefName(index).equals(fname)) + return null; + + try { + CtClass c = pool.get(cp.getFieldrefClassName(index)); + if (c == fclass || (!is_private && isFieldInSuper(c, fclass, fname))) + return cp.getFieldrefType(index); + } + catch (NotFoundException e) {} + return null; + } + + static boolean isFieldInSuper(CtClass clazz, CtClass fclass, String fname) { + if (!clazz.subclassOf(fclass)) + return false; + + try { + CtField f = clazz.getField(fname); + return f.getDeclaringClass() == fclass; + } + catch (NotFoundException e) {} + return false; + } + + public int transform(CtClass tclazz, int pos, CodeIterator iterator, + ConstPool cp) throws BadBytecode + { + int c = iterator.byteAt(pos); + if (c == GETFIELD || c == GETSTATIC) { + int index = iterator.u16bitAt(pos + 1); + String typedesc = isField(tclazz.getClassPool(), cp, + fieldClass, fieldname, isPrivate, index); + if (typedesc != null) { + if (c == GETSTATIC) { + iterator.move(pos); + pos = iterator.insertGap(1); // insertGap() may insert 4 bytes. + iterator.writeByte(ACONST_NULL, pos); + pos = iterator.next(); + } + + String type = "(Ljava/lang/Object;)" + typedesc; + int mi = cp.addClassInfo(methodClassname); + int methodref = cp.addMethodrefInfo(mi, methodName, type); + iterator.writeByte(INVOKESTATIC, pos); + iterator.write16bit(methodref, pos + 1); + return pos; + } + } + + return pos; + } +} diff --git a/src/main/javassist/convert/TransformWriteField.java b/src/main/javassist/convert/TransformWriteField.java new file mode 100644 index 0000000..2b6f8bb --- /dev/null +++ b/src/main/javassist/convert/TransformWriteField.java @@ -0,0 +1,71 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.convert; + +import javassist.CtClass; +import javassist.CtField; +import javassist.bytecode.*; + +final public class TransformWriteField extends TransformReadField { + public TransformWriteField(Transformer next, CtField field, + String methodClassname, String methodName) + { + super(next, field, methodClassname, methodName); + } + + public int transform(CtClass tclazz, int pos, CodeIterator iterator, + ConstPool cp) throws BadBytecode + { + int c = iterator.byteAt(pos); + if (c == PUTFIELD || c == PUTSTATIC) { + int index = iterator.u16bitAt(pos + 1); + String typedesc = isField(tclazz.getClassPool(), cp, + fieldClass, fieldname, isPrivate, index); + if (typedesc != null) { + if (c == PUTSTATIC) { + CodeAttribute ca = iterator.get(); + iterator.move(pos); + char c0 = typedesc.charAt(0); + if (c0 == 'J' || c0 == 'D') { // long or double + // insertGap() may insert 4 bytes. + pos = iterator.insertGap(3); + iterator.writeByte(ACONST_NULL, pos); + iterator.writeByte(DUP_X2, pos + 1); + iterator.writeByte(POP, pos + 2); + ca.setMaxStack(ca.getMaxStack() + 2); + } + else { + // insertGap() may insert 4 bytes. + pos = iterator.insertGap(2); + iterator.writeByte(ACONST_NULL, pos); + iterator.writeByte(SWAP, pos + 1); + ca.setMaxStack(ca.getMaxStack() + 1); + } + + pos = iterator.next(); + } + + int mi = cp.addClassInfo(methodClassname); + String type = "(Ljava/lang/Object;" + typedesc + ")V"; + int methodref = cp.addMethodrefInfo(mi, methodName, type); + iterator.writeByte(INVOKESTATIC, pos); + iterator.write16bit(methodref, pos + 1); + } + } + + return pos; + } +} diff --git a/src/main/javassist/convert/Transformer.java b/src/main/javassist/convert/Transformer.java new file mode 100644 index 0000000..4dd3807 --- /dev/null +++ b/src/main/javassist/convert/Transformer.java @@ -0,0 +1,56 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.convert; + +import javassist.CannotCompileException; +import javassist.CtClass; +import javassist.bytecode.BadBytecode; +import javassist.bytecode.CodeAttribute; +import javassist.bytecode.CodeIterator; +import javassist.bytecode.ConstPool; +import javassist.bytecode.MethodInfo; +import javassist.bytecode.Opcode; + +/** + * Transformer and its subclasses are used for executing + * code transformation specified by CodeConverter. + * + * @see javassist.CodeConverter + */ +public abstract class Transformer implements Opcode { + private Transformer next; + + public Transformer(Transformer t) { + next = t; + } + + public Transformer getNext() { return next; } + + public void initialize(ConstPool cp, CodeAttribute attr) {} + + public void initialize(ConstPool cp, CtClass clazz, MethodInfo minfo) throws CannotCompileException { + initialize(cp, minfo.getCodeAttribute()); + } + + public void clean() {} + + public abstract int transform(CtClass clazz, int pos, CodeIterator it, + ConstPool cp) throws CannotCompileException, BadBytecode; + + public int extraLocals() { return 0; } + + public int extraStack() { return 0; } +} diff --git a/src/main/javassist/expr/Cast.java b/src/main/javassist/expr/Cast.java new file mode 100644 index 0000000..1ef87be --- /dev/null +++ b/src/main/javassist/expr/Cast.java @@ -0,0 +1,165 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.expr; + +import javassist.*; +import javassist.bytecode.*; +import javassist.compiler.*; +import javassist.compiler.ast.ASTList; + +/** + * Explicit type cast. + */ +public class Cast extends Expr { + /** + * Undocumented constructor. Do not use; internal-use only. + */ + protected Cast(int pos, CodeIterator i, CtClass declaring, MethodInfo m) { + super(pos, i, declaring, m); + } + + /** + * Returns the method or constructor containing the type cast + * expression represented by this object. + */ + public CtBehavior where() { return super.where(); } + + /** + * Returns the line number of the source line containing the + * type-cast expression. + * + * @return -1 if this information is not available. + */ + public int getLineNumber() { + return super.getLineNumber(); + } + + /** + * Returns the source file containing the type-cast expression. + * + * @return null if this information is not available. + */ + public String getFileName() { + return super.getFileName(); + } + + /** + * Returns the <code>CtClass</code> object representing + * the type specified by the cast. + */ + public CtClass getType() throws NotFoundException { + ConstPool cp = getConstPool(); + int pos = currentPos; + int index = iterator.u16bitAt(pos + 1); + String name = cp.getClassInfo(index); + return thisClass.getClassPool().getCtClass(name); + } + + /** + * Returns the list of exceptions that the expression may throw. + * This list includes both the exceptions that the try-catch statements + * including the expression can catch and the exceptions that + * the throws declaration allows the method to throw. + */ + public CtClass[] mayThrow() { + return super.mayThrow(); + } + + /** + * Replaces the explicit cast operator with the bytecode derived from + * the given source text. + * + * <p>$0 is available but the value is <code>null</code>. + * + * @param statement a Java statement except try-catch. + */ + public void replace(String statement) throws CannotCompileException { + thisClass.getClassFile(); // to call checkModify(). + ConstPool constPool = getConstPool(); + int pos = currentPos; + int index = iterator.u16bitAt(pos + 1); + + Javac jc = new Javac(thisClass); + ClassPool cp = thisClass.getClassPool(); + CodeAttribute ca = iterator.get(); + + try { + CtClass[] params + = new CtClass[] { cp.get(javaLangObject) }; + CtClass retType = getType(); + + int paramVar = ca.getMaxLocals(); + jc.recordParams(javaLangObject, params, true, paramVar, + withinStatic()); + int retVar = jc.recordReturnType(retType, true); + jc.recordProceed(new ProceedForCast(index, retType)); + + /* Is $_ included in the source code? + */ + checkResultValue(retType, statement); + + Bytecode bytecode = jc.getBytecode(); + storeStack(params, true, paramVar, bytecode); + jc.recordLocalVariables(ca, pos); + + bytecode.addConstZero(retType); + bytecode.addStore(retVar, retType); // initialize $_ + + jc.compileStmnt(statement); + bytecode.addLoad(retVar, retType); + + replace0(pos, bytecode, 3); + } + catch (CompileError e) { throw new CannotCompileException(e); } + catch (NotFoundException e) { throw new CannotCompileException(e); } + catch (BadBytecode e) { + throw new CannotCompileException("broken method"); + } + } + + /* <type> $proceed(Object obj) + */ + static class ProceedForCast implements ProceedHandler { + int index; + CtClass retType; + + ProceedForCast(int i, CtClass t) { + index = i; + retType = t; + } + + public void doit(JvstCodeGen gen, Bytecode bytecode, ASTList args) + throws CompileError + { + if (gen.getMethodArgsLength(args) != 1) + throw new CompileError(Javac.proceedName + + "() cannot take more than one parameter " + + "for cast"); + + gen.atMethodArgs(args, new int[1], new int[1], new String[1]); + bytecode.addOpcode(Opcode.CHECKCAST); + bytecode.addIndex(index); + gen.setType(retType); + } + + public void setReturnType(JvstTypeChecker c, ASTList args) + throws CompileError + { + c.atMethodArgs(args, new int[1], new int[1], new String[1]); + c.setType(retType); + } + } +} diff --git a/src/main/javassist/expr/ConstructorCall.java b/src/main/javassist/expr/ConstructorCall.java new file mode 100644 index 0000000..3a6a02f --- /dev/null +++ b/src/main/javassist/expr/ConstructorCall.java @@ -0,0 +1,69 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.expr; + +import javassist.CtClass; +import javassist.CtConstructor; +import javassist.CtMethod; +import javassist.NotFoundException; +import javassist.bytecode.CodeIterator; +import javassist.bytecode.MethodInfo; + +/** + * Constructor call such as <code>this()</code> and <code>super()</code> + * within a constructor body. + * + * @see NewExpr + */ +public class ConstructorCall extends MethodCall { + /** + * Undocumented constructor. Do not use; internal-use only. + */ + protected ConstructorCall(int pos, CodeIterator i, CtClass decl, MethodInfo m) { + super(pos, i, decl, m); + } + + /** + * Returns <code>"super"</code> or "<code>"this"</code>. + */ + public String getMethodName() { + return isSuper() ? "super" : "this"; + } + + /** + * Always throws a <code>NotFoundException</code>. + * + * @see #getConstructor() + */ + public CtMethod getMethod() throws NotFoundException { + throw new NotFoundException("this is a constructor call. Call getConstructor()."); + } + + /** + * Returns the called constructor. + */ + public CtConstructor getConstructor() throws NotFoundException { + return getCtClass().getConstructor(getSignature()); + } + + /** + * Returns true if the called constructor is not <code>this()</code> + * but <code>super()</code> (a constructor declared in the super class). + */ + public boolean isSuper() { + return super.isSuper(); + } +} diff --git a/src/main/javassist/expr/Expr.java b/src/main/javassist/expr/Expr.java new file mode 100644 index 0000000..75de54a --- /dev/null +++ b/src/main/javassist/expr/Expr.java @@ -0,0 +1,329 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.expr; + +import javassist.CannotCompileException; +import javassist.ClassPool; +import javassist.CtBehavior; +import javassist.CtClass; +import javassist.CtConstructor; +import javassist.CtPrimitiveType; +import javassist.NotFoundException; +import javassist.bytecode.AccessFlag; +import javassist.bytecode.BadBytecode; +import javassist.bytecode.Bytecode; +import javassist.bytecode.ClassFile; +import javassist.bytecode.CodeAttribute; +import javassist.bytecode.CodeIterator; +import javassist.bytecode.ConstPool; +import javassist.bytecode.ExceptionTable; +import javassist.bytecode.ExceptionsAttribute; +import javassist.bytecode.MethodInfo; +import javassist.bytecode.Opcode; +import javassist.compiler.Javac; + +import java.util.Iterator; +import java.util.LinkedList; + +/** + * Expression. + */ +public abstract class Expr implements Opcode { + int currentPos; + CodeIterator iterator; + CtClass thisClass; + MethodInfo thisMethod; + boolean edited; + int maxLocals, maxStack; + + static final String javaLangObject = "java.lang.Object"; + + /** + * Undocumented constructor. Do not use; internal-use only. + */ + protected Expr(int pos, CodeIterator i, CtClass declaring, MethodInfo m) { + currentPos = pos; + iterator = i; + thisClass = declaring; + thisMethod = m; + } + + /** + * Returns the class that declares the method enclosing + * this expression. + * + * @since 3.7 + */ + public CtClass getEnclosingClass() { return thisClass; } + + protected final ConstPool getConstPool() { + return thisMethod.getConstPool(); + } + + protected final boolean edited() { + return edited; + } + + protected final int locals() { + return maxLocals; + } + + protected final int stack() { + return maxStack; + } + + /** + * Returns true if this method is static. + */ + protected final boolean withinStatic() { + return (thisMethod.getAccessFlags() & AccessFlag.STATIC) != 0; + } + + /** + * Returns the constructor or method containing the expression. + */ + public CtBehavior where() { + MethodInfo mi = thisMethod; + CtBehavior[] cb = thisClass.getDeclaredBehaviors(); + for (int i = cb.length - 1; i >= 0; --i) + if (cb[i].getMethodInfo2() == mi) + return cb[i]; + + CtConstructor init = thisClass.getClassInitializer(); + if (init != null && init.getMethodInfo2() == mi) + return init; + + /* getDeclaredBehaviors() returns a list of methods/constructors. + * Although the list is cached in a CtClass object, it might be + * recreated for some reason. Thus, the member name and the signature + * must be also checked. + */ + for (int i = cb.length - 1; i >= 0; --i) { + if (thisMethod.getName().equals(cb[i].getMethodInfo2().getName()) + && thisMethod.getDescriptor() + .equals(cb[i].getMethodInfo2().getDescriptor())) { + return cb[i]; + } + } + + throw new RuntimeException("fatal: not found"); + } + + /** + * Returns the list of exceptions that the expression may throw. This list + * includes both the exceptions that the try-catch statements including the + * expression can catch and the exceptions that the throws declaration + * allows the method to throw. + */ + public CtClass[] mayThrow() { + ClassPool pool = thisClass.getClassPool(); + ConstPool cp = thisMethod.getConstPool(); + LinkedList list = new LinkedList(); + try { + CodeAttribute ca = thisMethod.getCodeAttribute(); + ExceptionTable et = ca.getExceptionTable(); + int pos = currentPos; + int n = et.size(); + for (int i = 0; i < n; ++i) + if (et.startPc(i) <= pos && pos < et.endPc(i)) { + int t = et.catchType(i); + if (t > 0) + try { + addClass(list, pool.get(cp.getClassInfo(t))); + } + catch (NotFoundException e) { + } + } + } + catch (NullPointerException e) { + } + + ExceptionsAttribute ea = thisMethod.getExceptionsAttribute(); + if (ea != null) { + String[] exceptions = ea.getExceptions(); + if (exceptions != null) { + int n = exceptions.length; + for (int i = 0; i < n; ++i) + try { + addClass(list, pool.get(exceptions[i])); + } + catch (NotFoundException e) { + } + } + } + + return (CtClass[])list.toArray(new CtClass[list.size()]); + } + + private static void addClass(LinkedList list, CtClass c) { + Iterator it = list.iterator(); + while (it.hasNext()) + if (it.next() == c) + return; + + list.add(c); + } + + /** + * Returns the index of the bytecode corresponding to the expression. It is + * the index into the byte array containing the Java bytecode that + * implements the method. + */ + public int indexOfBytecode() { + return currentPos; + } + + /** + * Returns the line number of the source line containing the expression. + * + * @return -1 if this information is not available. + */ + public int getLineNumber() { + return thisMethod.getLineNumber(currentPos); + } + + /** + * Returns the source file containing the expression. + * + * @return null if this information is not available. + */ + public String getFileName() { + ClassFile cf = thisClass.getClassFile2(); + if (cf == null) + return null; + else + return cf.getSourceFile(); + } + + static final boolean checkResultValue(CtClass retType, String prog) + throws CannotCompileException { + /* + * Is $_ included in the source code? + */ + boolean hasIt = (prog.indexOf(Javac.resultVarName) >= 0); + if (!hasIt && retType != CtClass.voidType) + throw new CannotCompileException( + "the resulting value is not stored in " + + Javac.resultVarName); + + return hasIt; + } + + /* + * If isStaticCall is true, null is assigned to $0. So $0 must be declared + * by calling Javac.recordParams(). + * + * After executing this method, the current stack depth might be less than + * 0. + */ + static final void storeStack(CtClass[] params, boolean isStaticCall, + int regno, Bytecode bytecode) { + storeStack0(0, params.length, params, regno + 1, bytecode); + if (isStaticCall) + bytecode.addOpcode(ACONST_NULL); + + bytecode.addAstore(regno); + } + + private static void storeStack0(int i, int n, CtClass[] params, int regno, + Bytecode bytecode) { + if (i >= n) + return; + else { + CtClass c = params[i]; + int size; + if (c instanceof CtPrimitiveType) + size = ((CtPrimitiveType)c).getDataSize(); + else + size = 1; + + storeStack0(i + 1, n, params, regno + size, bytecode); + bytecode.addStore(regno, c); + } + } + + // The implementation of replace() should call thisClass.checkModify() + // so that isModify() will return true. Otherwise, thisClass.classfile + // might be released during compilation and the compiler might generate + // bytecode with a wrong copy of ConstPool. + + /** + * Replaces this expression with the bytecode derived from + * the given source text. + * + * @param statement a Java statement except try-catch. + */ + public abstract void replace(String statement) throws CannotCompileException; + + /** + * Replaces this expression with the bytecode derived from + * the given source text and <code>ExprEditor</code>. + * + * @param statement a Java statement except try-catch. + * @param recursive if not null, the substituted bytecode + * is recursively processed by the given + * <code>ExprEditor</code>. + * @since 3.1 + */ + public void replace(String statement, ExprEditor recursive) + throws CannotCompileException + { + replace(statement); + if (recursive != null) + runEditor(recursive, iterator); + } + + protected void replace0(int pos, Bytecode bytecode, int size) + throws BadBytecode { + byte[] code = bytecode.get(); + edited = true; + int gap = code.length - size; + for (int i = 0; i < size; ++i) + iterator.writeByte(NOP, pos + i); + + if (gap > 0) + pos = iterator.insertGapAt(pos, gap, false).position; + + iterator.write(code, pos); + iterator.insert(bytecode.getExceptionTable(), pos); + maxLocals = bytecode.getMaxLocals(); + maxStack = bytecode.getMaxStack(); + } + + protected void runEditor(ExprEditor ed, CodeIterator oldIterator) + throws CannotCompileException + { + CodeAttribute codeAttr = oldIterator.get(); + int orgLocals = codeAttr.getMaxLocals(); + int orgStack = codeAttr.getMaxStack(); + int newLocals = locals(); + codeAttr.setMaxStack(stack()); + codeAttr.setMaxLocals(newLocals); + ExprEditor.LoopContext context + = new ExprEditor.LoopContext(newLocals); + int size = oldIterator.getCodeLength(); + int endPos = oldIterator.lookAhead(); + oldIterator.move(currentPos); + if (ed.doit(thisClass, thisMethod, context, oldIterator, endPos)) + edited = true; + + oldIterator.move(endPos + oldIterator.getCodeLength() - size); + codeAttr.setMaxLocals(orgLocals); + codeAttr.setMaxStack(orgStack); + maxLocals = context.maxLocals; + maxStack += context.maxStack; + } +} diff --git a/src/main/javassist/expr/ExprEditor.java b/src/main/javassist/expr/ExprEditor.java new file mode 100644 index 0000000..80ddd4b --- /dev/null +++ b/src/main/javassist/expr/ExprEditor.java @@ -0,0 +1,316 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.expr; + +import javassist.bytecode.*; +import javassist.CtClass; +import javassist.CannotCompileException; + +/** + * A translator of method bodies. + * + * <p>The users can define a subclass of this class to customize how to + * modify a method body. The overall architecture is similar to the + * strategy pattern. + * + * <p>If <code>instrument()</code> is called in + * <code>CtMethod</code>, the method body is scanned from the beginning + * to the end. + * Whenever an expression, such as a method call and a <tt>new</tt> + * expression (object creation), + * is found, <code>edit()</code> is called in <code>ExprEdit</code>. + * <code>edit()</code> can inspect and modify the given expression. + * The modification is reflected on the original method body. If + * <code>edit()</code> does nothing, the original method body is not + * changed. + * + * <p>The following code is an example: + * + * <ul><pre> + * CtMethod cm = ...; + * cm.instrument(new ExprEditor() { + * public void edit(MethodCall m) throws CannotCompileException { + * if (m.getClassName().equals("Point")) { + * System.out.println(m.getMethodName() + " line: " + * + m.getLineNumber()); + * } + * }); + * </pre></ul> + * + * <p>This code inspects all method calls appearing in the method represented + * by <code>cm</code> and it prints the names and the line numbers of the + * methods declared in class <code>Point</code>. This code does not modify + * the body of the method represented by <code>cm</code>. If the method + * body must be modified, call <code>replace()</code> + * in <code>MethodCall</code>. + * + * @see javassist.CtClass#instrument(ExprEditor) + * @see javassist.CtMethod#instrument(ExprEditor) + * @see javassist.CtConstructor#instrument(ExprEditor) + * @see MethodCall + * @see NewExpr + * @see FieldAccess + * + * @see javassist.CodeConverter + */ +public class ExprEditor { + /** + * Default constructor. It does nothing. + */ + public ExprEditor() {} + + /** + * Undocumented method. Do not use; internal-use only. + */ + public boolean doit(CtClass clazz, MethodInfo minfo) + throws CannotCompileException + { + CodeAttribute codeAttr = minfo.getCodeAttribute(); + if (codeAttr == null) + return false; + + CodeIterator iterator = codeAttr.iterator(); + boolean edited = false; + LoopContext context = new LoopContext(codeAttr.getMaxLocals()); + + while (iterator.hasNext()) + if (loopBody(iterator, clazz, minfo, context)) + edited = true; + + ExceptionTable et = codeAttr.getExceptionTable(); + int n = et.size(); + for (int i = 0; i < n; ++i) { + Handler h = new Handler(et, i, iterator, clazz, minfo); + edit(h); + if (h.edited()) { + edited = true; + context.updateMax(h.locals(), h.stack()); + } + } + + // codeAttr might be modified by other partiess + // so I check the current value of max-locals. + if (codeAttr.getMaxLocals() < context.maxLocals) + codeAttr.setMaxLocals(context.maxLocals); + + codeAttr.setMaxStack(codeAttr.getMaxStack() + context.maxStack); + try { + if (edited) + minfo.rebuildStackMapIf6(clazz.getClassPool(), + clazz.getClassFile2()); + } + catch (BadBytecode b) { + throw new CannotCompileException(b.getMessage(), b); + } + + return edited; + } + + /** + * Visits each bytecode in the given range. + */ + boolean doit(CtClass clazz, MethodInfo minfo, LoopContext context, + CodeIterator iterator, int endPos) + throws CannotCompileException + { + boolean edited = false; + while (iterator.hasNext() && iterator.lookAhead() < endPos) { + int size = iterator.getCodeLength(); + if (loopBody(iterator, clazz, minfo, context)) { + edited = true; + int size2 = iterator.getCodeLength(); + if (size != size2) // the body was modified. + endPos += size2 - size; + } + } + + return edited; + } + + final static class NewOp { + NewOp next; + int pos; + String type; + + NewOp(NewOp n, int p, String t) { + next = n; + pos = p; + type = t; + } + } + + final static class LoopContext { + NewOp newList; + int maxLocals; + int maxStack; + + LoopContext(int locals) { + maxLocals = locals; + maxStack = 0; + newList = null; + } + + void updateMax(int locals, int stack) { + if (maxLocals < locals) + maxLocals = locals; + + if (maxStack < stack) + maxStack = stack; + } + } + + final boolean loopBody(CodeIterator iterator, CtClass clazz, + MethodInfo minfo, LoopContext context) + throws CannotCompileException + { + try { + Expr expr = null; + int pos = iterator.next(); + int c = iterator.byteAt(pos); + + if (c < Opcode.GETSTATIC) // c < 178 + /* skip */; + else if (c < Opcode.NEWARRAY) { // c < 188 + if (c == Opcode.INVOKESTATIC + || c == Opcode.INVOKEINTERFACE + || c == Opcode.INVOKEVIRTUAL) { + expr = new MethodCall(pos, iterator, clazz, minfo); + edit((MethodCall)expr); + } + else if (c == Opcode.GETFIELD || c == Opcode.GETSTATIC + || c == Opcode.PUTFIELD + || c == Opcode.PUTSTATIC) { + expr = new FieldAccess(pos, iterator, clazz, minfo, c); + edit((FieldAccess)expr); + } + else if (c == Opcode.NEW) { + int index = iterator.u16bitAt(pos + 1); + context.newList = new NewOp(context.newList, pos, + minfo.getConstPool().getClassInfo(index)); + } + else if (c == Opcode.INVOKESPECIAL) { + NewOp newList = context.newList; + if (newList != null + && minfo.getConstPool().isConstructor(newList.type, + iterator.u16bitAt(pos + 1)) > 0) { + expr = new NewExpr(pos, iterator, clazz, minfo, + newList.type, newList.pos); + edit((NewExpr)expr); + context.newList = newList.next; + } + else { + MethodCall mcall = new MethodCall(pos, iterator, clazz, minfo); + if (mcall.getMethodName().equals(MethodInfo.nameInit)) { + ConstructorCall ccall = new ConstructorCall(pos, iterator, clazz, minfo); + expr = ccall; + edit(ccall); + } + else { + expr = mcall; + edit(mcall); + } + } + } + } + else { // c >= 188 + if (c == Opcode.NEWARRAY || c == Opcode.ANEWARRAY + || c == Opcode.MULTIANEWARRAY) { + expr = new NewArray(pos, iterator, clazz, minfo, c); + edit((NewArray)expr); + } + else if (c == Opcode.INSTANCEOF) { + expr = new Instanceof(pos, iterator, clazz, minfo); + edit((Instanceof)expr); + } + else if (c == Opcode.CHECKCAST) { + expr = new Cast(pos, iterator, clazz, minfo); + edit((Cast)expr); + } + } + + if (expr != null && expr.edited()) { + context.updateMax(expr.locals(), expr.stack()); + return true; + } + else + return false; + } + catch (BadBytecode e) { + throw new CannotCompileException(e); + } + } + + /** + * Edits a <tt>new</tt> expression (overridable). + * The default implementation performs nothing. + * + * @param e the <tt>new</tt> expression creating an object. + */ + public void edit(NewExpr e) throws CannotCompileException {} + + /** + * Edits an expression for array creation (overridable). + * The default implementation performs nothing. + * + * @param a the <tt>new</tt> expression for creating an array. + * @throws CannotCompileException + */ + public void edit(NewArray a) throws CannotCompileException {} + + /** + * Edits a method call (overridable). + * + * The default implementation performs nothing. + */ + public void edit(MethodCall m) throws CannotCompileException {} + + /** + * Edits a constructor call (overridable). + * The constructor call is either + * <code>super()</code> or <code>this()</code> + * included in a constructor body. + * + * The default implementation performs nothing. + * + * @see #edit(NewExpr) + */ + public void edit(ConstructorCall c) throws CannotCompileException {} + + /** + * Edits a field-access expression (overridable). + * Field access means both read and write. + * The default implementation performs nothing. + */ + public void edit(FieldAccess f) throws CannotCompileException {} + + /** + * Edits an instanceof expression (overridable). + * The default implementation performs nothing. + */ + public void edit(Instanceof i) throws CannotCompileException {} + + /** + * Edits an expression for explicit type casting (overridable). + * The default implementation performs nothing. + */ + public void edit(Cast c) throws CannotCompileException {} + + /** + * Edits a catch clause (overridable). + * The default implementation performs nothing. + */ + public void edit(Handler h) throws CannotCompileException {} +} diff --git a/src/main/javassist/expr/FieldAccess.java b/src/main/javassist/expr/FieldAccess.java new file mode 100644 index 0000000..56ead16 --- /dev/null +++ b/src/main/javassist/expr/FieldAccess.java @@ -0,0 +1,322 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.expr; + +import javassist.*; +import javassist.bytecode.*; +import javassist.compiler.*; +import javassist.compiler.ast.ASTList; + +/** + * Expression for accessing a field. + */ +public class FieldAccess extends Expr { + int opcode; + + protected FieldAccess(int pos, CodeIterator i, CtClass declaring, + MethodInfo m, int op) { + super(pos, i, declaring, m); + opcode = op; + } + + /** + * Returns the method or constructor containing the field-access + * expression represented by this object. + */ + public CtBehavior where() { return super.where(); } + + /** + * Returns the line number of the source line containing the + * field access. + * + * @return -1 if this information is not available. + */ + public int getLineNumber() { + return super.getLineNumber(); + } + + /** + * Returns the source file containing the field access. + * + * @return null if this information is not available. + */ + public String getFileName() { + return super.getFileName(); + } + + /** + * Returns true if the field is static. + */ + public boolean isStatic() { + return isStatic(opcode); + } + + static boolean isStatic(int c) { + return c == Opcode.GETSTATIC || c == Opcode.PUTSTATIC; + } + + /** + * Returns true if the field is read. + */ + public boolean isReader() { + return opcode == Opcode.GETFIELD || opcode == Opcode.GETSTATIC; + } + + /** + * Returns true if the field is written in. + */ + public boolean isWriter() { + return opcode == Opcode.PUTFIELD || opcode == Opcode.PUTSTATIC; + } + + /** + * Returns the class in which the field is declared. + */ + private CtClass getCtClass() throws NotFoundException { + return thisClass.getClassPool().get(getClassName()); + } + + /** + * Returns the name of the class in which the field is declared. + */ + public String getClassName() { + int index = iterator.u16bitAt(currentPos + 1); + return getConstPool().getFieldrefClassName(index); + } + + /** + * Returns the name of the field. + */ + public String getFieldName() { + int index = iterator.u16bitAt(currentPos + 1); + return getConstPool().getFieldrefName(index); + } + + /** + * Returns the field accessed by this expression. + */ + public CtField getField() throws NotFoundException { + CtClass cc = getCtClass(); + return cc.getField(getFieldName()); + } + + /** + * Returns the list of exceptions that the expression may throw. + * This list includes both the exceptions that the try-catch statements + * including the expression can catch and the exceptions that + * the throws declaration allows the method to throw. + */ + public CtClass[] mayThrow() { + return super.mayThrow(); + } + + /** + * Returns the signature of the field type. + * The signature is represented by a character string + * called field descriptor, which is defined in the JVM specification. + * + * @see javassist.bytecode.Descriptor#toCtClass(String, ClassPool) + * @since 3.1 + */ + public String getSignature() { + int index = iterator.u16bitAt(currentPos + 1); + return getConstPool().getFieldrefType(index); + } + + /** + * Replaces the method call with the bytecode derived from + * the given source text. + * + * <p>$0 is available even if the called method is static. + * If the field access is writing, $_ is available but the value + * of $_ is ignored. + * + * @param statement a Java statement except try-catch. + */ + public void replace(String statement) throws CannotCompileException { + thisClass.getClassFile(); // to call checkModify(). + ConstPool constPool = getConstPool(); + int pos = currentPos; + int index = iterator.u16bitAt(pos + 1); + + Javac jc = new Javac(thisClass); + CodeAttribute ca = iterator.get(); + try { + CtClass[] params; + CtClass retType; + CtClass fieldType + = Descriptor.toCtClass(constPool.getFieldrefType(index), + thisClass.getClassPool()); + boolean read = isReader(); + if (read) { + params = new CtClass[0]; + retType = fieldType; + } + else { + params = new CtClass[1]; + params[0] = fieldType; + retType = CtClass.voidType; + } + + int paramVar = ca.getMaxLocals(); + jc.recordParams(constPool.getFieldrefClassName(index), params, + true, paramVar, withinStatic()); + + /* Is $_ included in the source code? + */ + boolean included = checkResultValue(retType, statement); + if (read) + included = true; + + int retVar = jc.recordReturnType(retType, included); + if (read) + jc.recordProceed(new ProceedForRead(retType, opcode, + index, paramVar)); + else { + // because $type is not the return type... + jc.recordType(fieldType); + jc.recordProceed(new ProceedForWrite(params[0], opcode, + index, paramVar)); + } + + Bytecode bytecode = jc.getBytecode(); + storeStack(params, isStatic(), paramVar, bytecode); + jc.recordLocalVariables(ca, pos); + + if (included) + if (retType == CtClass.voidType) { + bytecode.addOpcode(ACONST_NULL); + bytecode.addAstore(retVar); + } + else { + bytecode.addConstZero(retType); + bytecode.addStore(retVar, retType); // initialize $_ + } + + jc.compileStmnt(statement); + if (read) + bytecode.addLoad(retVar, retType); + + replace0(pos, bytecode, 3); + } + catch (CompileError e) { throw new CannotCompileException(e); } + catch (NotFoundException e) { throw new CannotCompileException(e); } + catch (BadBytecode e) { + throw new CannotCompileException("broken method"); + } + } + + /* <field type> $proceed() + */ + static class ProceedForRead implements ProceedHandler { + CtClass fieldType; + int opcode; + int targetVar, index; + + ProceedForRead(CtClass type, int op, int i, int var) { + fieldType = type; + targetVar = var; + opcode = op; + index = i; + } + + public void doit(JvstCodeGen gen, Bytecode bytecode, ASTList args) + throws CompileError + { + if (args != null && !gen.isParamListName(args)) + throw new CompileError(Javac.proceedName + + "() cannot take a parameter for field reading"); + + int stack; + if (isStatic(opcode)) + stack = 0; + else { + stack = -1; + bytecode.addAload(targetVar); + } + + if (fieldType instanceof CtPrimitiveType) + stack += ((CtPrimitiveType)fieldType).getDataSize(); + else + ++stack; + + bytecode.add(opcode); + bytecode.addIndex(index); + bytecode.growStack(stack); + gen.setType(fieldType); + } + + public void setReturnType(JvstTypeChecker c, ASTList args) + throws CompileError + { + c.setType(fieldType); + } + } + + /* void $proceed(<field type>) + * the return type is not the field type but void. + */ + static class ProceedForWrite implements ProceedHandler { + CtClass fieldType; + int opcode; + int targetVar, index; + + ProceedForWrite(CtClass type, int op, int i, int var) { + fieldType = type; + targetVar = var; + opcode = op; + index = i; + } + + public void doit(JvstCodeGen gen, Bytecode bytecode, ASTList args) + throws CompileError + { + if (gen.getMethodArgsLength(args) != 1) + throw new CompileError(Javac.proceedName + + "() cannot take more than one parameter " + + "for field writing"); + + int stack; + if (isStatic(opcode)) + stack = 0; + else { + stack = -1; + bytecode.addAload(targetVar); + } + + gen.atMethodArgs(args, new int[1], new int[1], new String[1]); + gen.doNumCast(fieldType); + if (fieldType instanceof CtPrimitiveType) + stack -= ((CtPrimitiveType)fieldType).getDataSize(); + else + --stack; + + bytecode.add(opcode); + bytecode.addIndex(index); + bytecode.growStack(stack); + gen.setType(CtClass.voidType); + gen.addNullIfVoid(); + } + + public void setReturnType(JvstTypeChecker c, ASTList args) + throws CompileError + { + c.atMethodArgs(args, new int[1], new int[1], new String[1]); + c.setType(CtClass.voidType); + c.addNullIfVoid(); + } + } +} diff --git a/src/main/javassist/expr/Handler.java b/src/main/javassist/expr/Handler.java new file mode 100644 index 0000000..dd7e53f --- /dev/null +++ b/src/main/javassist/expr/Handler.java @@ -0,0 +1,145 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.expr; + +import javassist.*; +import javassist.bytecode.*; +import javassist.compiler.*; + +/** + * A <code>catch</code> clause or a <code>finally</code> block. + */ +public class Handler extends Expr { + private static String EXCEPTION_NAME = "$1"; + private ExceptionTable etable; + private int index; + + /** + * Undocumented constructor. Do not use; internal-use only. + */ + protected Handler(ExceptionTable et, int nth, + CodeIterator it, CtClass declaring, MethodInfo m) { + super(et.handlerPc(nth), it, declaring, m); + etable = et; + index = nth; + } + + /** + * Returns the method or constructor containing the catch clause. + */ + public CtBehavior where() { return super.where(); } + + /** + * Returns the source line number of the catch clause. + * + * @return -1 if this information is not available. + */ + public int getLineNumber() { + return super.getLineNumber(); + } + + /** + * Returns the source file containing the catch clause. + * + * @return null if this information is not available. + */ + public String getFileName() { + return super.getFileName(); + } + + /** + * Returns the list of exceptions that the catch clause may throw. + */ + public CtClass[] mayThrow() { + return super.mayThrow(); + } + + /** + * Returns the type handled by the catch clause. + * If this is a <code>finally</code> block, <code>null</code> is returned. + */ + public CtClass getType() throws NotFoundException { + int type = etable.catchType(index); + if (type == 0) + return null; + else { + ConstPool cp = getConstPool(); + String name = cp.getClassInfo(type); + return thisClass.getClassPool().getCtClass(name); + } + } + + /** + * Returns true if this is a <code>finally</code> block. + */ + public boolean isFinally() { + return etable.catchType(index) == 0; + } + + /** + * This method has not been implemented yet. + * + * @param statement a Java statement except try-catch. + */ + public void replace(String statement) throws CannotCompileException { + throw new RuntimeException("not implemented yet"); + } + + /** + * Inserts bytecode at the beginning of the catch clause. + * The caught exception is stored in <code>$1</code>. + * + * @param src the source code representing the inserted bytecode. + * It must be a single statement or block. + */ + public void insertBefore(String src) throws CannotCompileException { + edited = true; + + ConstPool cp = getConstPool(); + CodeAttribute ca = iterator.get(); + Javac jv = new Javac(thisClass); + Bytecode b = jv.getBytecode(); + b.setStackDepth(1); + b.setMaxLocals(ca.getMaxLocals()); + + try { + CtClass type = getType(); + int var = jv.recordVariable(type, EXCEPTION_NAME); + jv.recordReturnType(type, false); + b.addAstore(var); + jv.compileStmnt(src); + b.addAload(var); + + int oldHandler = etable.handlerPc(index); + b.addOpcode(Opcode.GOTO); + b.addIndex(oldHandler - iterator.getCodeLength() + - b.currentPc() + 1); + + maxStack = b.getMaxStack(); + maxLocals = b.getMaxLocals(); + + int pos = iterator.append(b.get()); + iterator.append(b.getExceptionTable(), pos); + etable.setHandlerPc(index, pos); + } + catch (NotFoundException e) { + throw new CannotCompileException(e); + } + catch (CompileError e) { + throw new CannotCompileException(e); + } + } +} diff --git a/src/main/javassist/expr/Instanceof.java b/src/main/javassist/expr/Instanceof.java new file mode 100644 index 0000000..1ceed13 --- /dev/null +++ b/src/main/javassist/expr/Instanceof.java @@ -0,0 +1,169 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.expr; + +import javassist.*; +import javassist.bytecode.*; +import javassist.compiler.*; +import javassist.compiler.ast.ASTList; + +/** + * Instanceof operator. + */ +public class Instanceof extends Expr { + /** + * Undocumented constructor. Do not use; internal-use only. + */ + protected Instanceof(int pos, CodeIterator i, CtClass declaring, + MethodInfo m) { + super(pos, i, declaring, m); + } + + /** + * Returns the method or constructor containing the instanceof + * expression represented by this object. + */ + public CtBehavior where() { return super.where(); } + + /** + * Returns the line number of the source line containing the + * instanceof expression. + * + * @return -1 if this information is not available. + */ + public int getLineNumber() { + return super.getLineNumber(); + } + + /** + * Returns the source file containing the + * instanceof expression. + * + * @return null if this information is not available. + */ + public String getFileName() { + return super.getFileName(); + } + + /** + * Returns the <code>CtClass</code> object representing + * the type name on the right hand side + * of the instanceof operator. + */ + public CtClass getType() throws NotFoundException { + ConstPool cp = getConstPool(); + int pos = currentPos; + int index = iterator.u16bitAt(pos + 1); + String name = cp.getClassInfo(index); + return thisClass.getClassPool().getCtClass(name); + } + + /** + * Returns the list of exceptions that the expression may throw. + * This list includes both the exceptions that the try-catch statements + * including the expression can catch and the exceptions that + * the throws declaration allows the method to throw. + */ + public CtClass[] mayThrow() { + return super.mayThrow(); + } + + /** + * Replaces the instanceof operator with the bytecode derived from + * the given source text. + * + * <p>$0 is available but the value is <code>null</code>. + * + * @param statement a Java statement except try-catch. + */ + public void replace(String statement) throws CannotCompileException { + thisClass.getClassFile(); // to call checkModify(). + ConstPool constPool = getConstPool(); + int pos = currentPos; + int index = iterator.u16bitAt(pos + 1); + + Javac jc = new Javac(thisClass); + ClassPool cp = thisClass.getClassPool(); + CodeAttribute ca = iterator.get(); + + try { + CtClass[] params + = new CtClass[] { cp.get(javaLangObject) }; + CtClass retType = CtClass.booleanType; + + int paramVar = ca.getMaxLocals(); + jc.recordParams(javaLangObject, params, true, paramVar, + withinStatic()); + int retVar = jc.recordReturnType(retType, true); + jc.recordProceed(new ProceedForInstanceof(index)); + + // because $type is not the return type... + jc.recordType(getType()); + + /* Is $_ included in the source code? + */ + checkResultValue(retType, statement); + + Bytecode bytecode = jc.getBytecode(); + storeStack(params, true, paramVar, bytecode); + jc.recordLocalVariables(ca, pos); + + bytecode.addConstZero(retType); + bytecode.addStore(retVar, retType); // initialize $_ + + jc.compileStmnt(statement); + bytecode.addLoad(retVar, retType); + + replace0(pos, bytecode, 3); + } + catch (CompileError e) { throw new CannotCompileException(e); } + catch (NotFoundException e) { throw new CannotCompileException(e); } + catch (BadBytecode e) { + throw new CannotCompileException("broken method"); + } + } + + /* boolean $proceed(Object obj) + */ + static class ProceedForInstanceof implements ProceedHandler { + int index; + + ProceedForInstanceof(int i) { + index = i; + } + + public void doit(JvstCodeGen gen, Bytecode bytecode, ASTList args) + throws CompileError + { + if (gen.getMethodArgsLength(args) != 1) + throw new CompileError(Javac.proceedName + + "() cannot take more than one parameter " + + "for instanceof"); + + gen.atMethodArgs(args, new int[1], new int[1], new String[1]); + bytecode.addOpcode(Opcode.INSTANCEOF); + bytecode.addIndex(index); + gen.setType(CtClass.booleanType); + } + + public void setReturnType(JvstTypeChecker c, ASTList args) + throws CompileError + { + c.atMethodArgs(args, new int[1], new int[1], new String[1]); + c.setType(CtClass.booleanType); + } + } +} diff --git a/src/main/javassist/expr/MethodCall.java b/src/main/javassist/expr/MethodCall.java new file mode 100644 index 0000000..9e9d1db --- /dev/null +++ b/src/main/javassist/expr/MethodCall.java @@ -0,0 +1,246 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.expr; + +import javassist.*; +import javassist.bytecode.*; +import javassist.compiler.*; + +/** + * Method invocation (caller-side expression). + */ +public class MethodCall extends Expr { + /** + * Undocumented constructor. Do not use; internal-use only. + */ + protected MethodCall(int pos, CodeIterator i, CtClass declaring, + MethodInfo m) { + super(pos, i, declaring, m); + } + + private int getNameAndType(ConstPool cp) { + int pos = currentPos; + int c = iterator.byteAt(pos); + int index = iterator.u16bitAt(pos + 1); + + if (c == INVOKEINTERFACE) + return cp.getInterfaceMethodrefNameAndType(index); + else + return cp.getMethodrefNameAndType(index); + } + + /** + * Returns the method or constructor containing the method-call + * expression represented by this object. + */ + public CtBehavior where() { return super.where(); } + + /** + * Returns the line number of the source line containing the + * method call. + * + * @return -1 if this information is not available. + */ + public int getLineNumber() { + return super.getLineNumber(); + } + + /** + * Returns the source file containing the method call. + * + * @return null if this information is not available. + */ + public String getFileName() { + return super.getFileName(); + } + + /** + * Returns the class of the target object, + * which the method is called on. + */ + protected CtClass getCtClass() throws NotFoundException { + return thisClass.getClassPool().get(getClassName()); + } + + /** + * Returns the class name of the target object, + * which the method is called on. + */ + public String getClassName() { + String cname; + + ConstPool cp = getConstPool(); + int pos = currentPos; + int c = iterator.byteAt(pos); + int index = iterator.u16bitAt(pos + 1); + + if (c == INVOKEINTERFACE) + cname = cp.getInterfaceMethodrefClassName(index); + else + cname = cp.getMethodrefClassName(index); + + if (cname.charAt(0) == '[') + cname = Descriptor.toClassName(cname); + + return cname; + } + + /** + * Returns the name of the called method. + */ + public String getMethodName() { + ConstPool cp = getConstPool(); + int nt = getNameAndType(cp); + return cp.getUtf8Info(cp.getNameAndTypeName(nt)); + } + + /** + * Returns the called method. + */ + public CtMethod getMethod() throws NotFoundException { + return getCtClass().getMethod(getMethodName(), getSignature()); + } + + /** + * Returns the method signature (the parameter types + * and the return type). + * The method signature is represented by a character string + * called method descriptor, which is defined in the JVM specification. + * + * @see javassist.CtBehavior#getSignature() + * @see javassist.bytecode.Descriptor + * @since 3.1 + */ + public String getSignature() { + ConstPool cp = getConstPool(); + int nt = getNameAndType(cp); + return cp.getUtf8Info(cp.getNameAndTypeDescriptor(nt)); + } + + /** + * Returns the list of exceptions that the expression may throw. + * This list includes both the exceptions that the try-catch statements + * including the expression can catch and the exceptions that + * the throws declaration allows the method to throw. + */ + public CtClass[] mayThrow() { + return super.mayThrow(); + } + + /** + * Returns true if the called method is of a superclass of the current + * class. + */ + public boolean isSuper() { + return iterator.byteAt(currentPos) == INVOKESPECIAL + && !where().getDeclaringClass().getName().equals(getClassName()); + } + + /* + * Returns the parameter types of the called method. + + public CtClass[] getParameterTypes() throws NotFoundException { + return Descriptor.getParameterTypes(getMethodDesc(), + thisClass.getClassPool()); + } + */ + + /* + * Returns the return type of the called method. + + public CtClass getReturnType() throws NotFoundException { + return Descriptor.getReturnType(getMethodDesc(), + thisClass.getClassPool()); + } + */ + + /** + * Replaces the method call with the bytecode derived from + * the given source text. + * + * <p>$0 is available even if the called method is static. + * + * @param statement a Java statement except try-catch. + */ + public void replace(String statement) throws CannotCompileException { + thisClass.getClassFile(); // to call checkModify(). + ConstPool constPool = getConstPool(); + int pos = currentPos; + int index = iterator.u16bitAt(pos + 1); + + String classname, methodname, signature; + int opcodeSize; + int c = iterator.byteAt(pos); + if (c == INVOKEINTERFACE) { + opcodeSize = 5; + classname = constPool.getInterfaceMethodrefClassName(index); + methodname = constPool.getInterfaceMethodrefName(index); + signature = constPool.getInterfaceMethodrefType(index); + } + else if (c == INVOKESTATIC + || c == INVOKESPECIAL || c == INVOKEVIRTUAL) { + opcodeSize = 3; + classname = constPool.getMethodrefClassName(index); + methodname = constPool.getMethodrefName(index); + signature = constPool.getMethodrefType(index); + } + else + throw new CannotCompileException("not method invocation"); + + Javac jc = new Javac(thisClass); + ClassPool cp = thisClass.getClassPool(); + CodeAttribute ca = iterator.get(); + try { + CtClass[] params = Descriptor.getParameterTypes(signature, cp); + CtClass retType = Descriptor.getReturnType(signature, cp); + int paramVar = ca.getMaxLocals(); + jc.recordParams(classname, params, + true, paramVar, withinStatic()); + int retVar = jc.recordReturnType(retType, true); + if (c == INVOKESTATIC) + jc.recordStaticProceed(classname, methodname); + else if (c == INVOKESPECIAL) + jc.recordSpecialProceed(Javac.param0Name, classname, + methodname, signature); + else + jc.recordProceed(Javac.param0Name, methodname); + + /* Is $_ included in the source code? + */ + checkResultValue(retType, statement); + + Bytecode bytecode = jc.getBytecode(); + storeStack(params, c == INVOKESTATIC, paramVar, bytecode); + jc.recordLocalVariables(ca, pos); + + if (retType != CtClass.voidType) { + bytecode.addConstZero(retType); + bytecode.addStore(retVar, retType); // initialize $_ + } + + jc.compileStmnt(statement); + if (retType != CtClass.voidType) + bytecode.addLoad(retVar, retType); + + replace0(pos, bytecode, opcodeSize); + } + catch (CompileError e) { throw new CannotCompileException(e); } + catch (NotFoundException e) { throw new CannotCompileException(e); } + catch (BadBytecode e) { + throw new CannotCompileException("broken method"); + } + } +} diff --git a/src/main/javassist/expr/NewArray.java b/src/main/javassist/expr/NewArray.java new file mode 100644 index 0000000..c5ac41e --- /dev/null +++ b/src/main/javassist/expr/NewArray.java @@ -0,0 +1,282 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.expr; + +import javassist.*; +import javassist.bytecode.*; +import javassist.compiler.*; +import javassist.compiler.ast.ASTList; + +/** + * Array creation. + * + * <p>This class does not provide methods for obtaining the initial + * values of array elements. + */ +public class NewArray extends Expr { + int opcode; + + protected NewArray(int pos, CodeIterator i, CtClass declaring, + MethodInfo m, int op) { + super(pos, i, declaring, m); + opcode = op; + } + + /** + * Returns the method or constructor containing the array creation + * represented by this object. + */ + public CtBehavior where() { return super.where(); } + + /** + * Returns the line number of the source line containing the + * array creation. + * + * @return -1 if this information is not available. + */ + public int getLineNumber() { + return super.getLineNumber(); + } + + /** + * Returns the source file containing the array creation. + * + * @return null if this information is not available. + */ + public String getFileName() { + return super.getFileName(); + } + + /** + * Returns the list of exceptions that the expression may throw. + * This list includes both the exceptions that the try-catch statements + * including the expression can catch and the exceptions that + * the throws declaration allows the method to throw. + */ + public CtClass[] mayThrow() { + return super.mayThrow(); + } + + /** + * Returns the type of array components. If the created array is + * a two-dimensional array of <tt>int</tt>, + * the type returned by this method is + * not <tt>int[]</tt> but <tt>int</tt>. + */ + public CtClass getComponentType() throws NotFoundException { + if (opcode == Opcode.NEWARRAY) { + int atype = iterator.byteAt(currentPos + 1); + return getPrimitiveType(atype); + } + else if (opcode == Opcode.ANEWARRAY + || opcode == Opcode.MULTIANEWARRAY) { + int index = iterator.u16bitAt(currentPos + 1); + String desc = getConstPool().getClassInfo(index); + int dim = Descriptor.arrayDimension(desc); + desc = Descriptor.toArrayComponent(desc, dim); + return Descriptor.toCtClass(desc, thisClass.getClassPool()); + } + else + throw new RuntimeException("bad opcode: " + opcode); + } + + CtClass getPrimitiveType(int atype) { + switch (atype) { + case Opcode.T_BOOLEAN : + return CtClass.booleanType; + case Opcode.T_CHAR : + return CtClass.charType; + case Opcode.T_FLOAT : + return CtClass.floatType; + case Opcode.T_DOUBLE : + return CtClass.doubleType; + case Opcode.T_BYTE : + return CtClass.byteType; + case Opcode.T_SHORT : + return CtClass.shortType; + case Opcode.T_INT : + return CtClass.intType; + case Opcode.T_LONG : + return CtClass.longType; + default : + throw new RuntimeException("bad atype: " + atype); + } + } + + /** + * Returns the dimension of the created array. + */ + public int getDimension() { + if (opcode == Opcode.NEWARRAY) + return 1; + else if (opcode == Opcode.ANEWARRAY + || opcode == Opcode.MULTIANEWARRAY) { + int index = iterator.u16bitAt(currentPos + 1); + String desc = getConstPool().getClassInfo(index); + return Descriptor.arrayDimension(desc) + + (opcode == Opcode.ANEWARRAY ? 1 : 0); + } + else + throw new RuntimeException("bad opcode: " + opcode); + } + + /** + * Returns the number of dimensions of arrays to be created. + * If the opcode is multianewarray, this method returns the second + * operand. Otherwise, it returns 1. + */ + public int getCreatedDimensions() { + if (opcode == Opcode.MULTIANEWARRAY) + return iterator.byteAt(currentPos + 3); + else + return 1; + } + + /** + * Replaces the array creation with the bytecode derived from + * the given source text. + * + * <p>$0 is available even if the called method is static. + * If the field access is writing, $_ is available but the value + * of $_ is ignored. + * + * @param statement a Java statement except try-catch. + */ + public void replace(String statement) throws CannotCompileException { + try { + replace2(statement); + } + catch (CompileError e) { throw new CannotCompileException(e); } + catch (NotFoundException e) { throw new CannotCompileException(e); } + catch (BadBytecode e) { + throw new CannotCompileException("broken method"); + } + } + + private void replace2(String statement) + throws CompileError, NotFoundException, BadBytecode, + CannotCompileException + { + thisClass.getClassFile(); // to call checkModify(). + ConstPool constPool = getConstPool(); + int pos = currentPos; + CtClass retType; + int codeLength; + int index = 0; + int dim = 1; + String desc; + if (opcode == Opcode.NEWARRAY) { + index = iterator.byteAt(currentPos + 1); // atype + CtPrimitiveType cpt = (CtPrimitiveType)getPrimitiveType(index); + desc = "[" + cpt.getDescriptor(); + codeLength = 2; + } + else if (opcode == Opcode.ANEWARRAY) { + index = iterator.u16bitAt(pos + 1); + desc = constPool.getClassInfo(index); + if (desc.startsWith("[")) + desc = "[" + desc; + else + desc = "[L" + desc + ";"; + + codeLength = 3; + } + else if (opcode == Opcode.MULTIANEWARRAY) { + index = iterator.u16bitAt(currentPos + 1); + desc = constPool.getClassInfo(index); + dim = iterator.byteAt(currentPos + 3); + codeLength = 4; + } + else + throw new RuntimeException("bad opcode: " + opcode); + + retType = Descriptor.toCtClass(desc, thisClass.getClassPool()); + + Javac jc = new Javac(thisClass); + CodeAttribute ca = iterator.get(); + + CtClass[] params = new CtClass[dim]; + for (int i = 0; i < dim; ++i) + params[i] = CtClass.intType; + + int paramVar = ca.getMaxLocals(); + jc.recordParams(javaLangObject, params, + true, paramVar, withinStatic()); + + /* Is $_ included in the source code? + */ + checkResultValue(retType, statement); + int retVar = jc.recordReturnType(retType, true); + jc.recordProceed(new ProceedForArray(retType, opcode, index, dim)); + + Bytecode bytecode = jc.getBytecode(); + storeStack(params, true, paramVar, bytecode); + jc.recordLocalVariables(ca, pos); + + bytecode.addOpcode(ACONST_NULL); // initialize $_ + bytecode.addAstore(retVar); + + jc.compileStmnt(statement); + bytecode.addAload(retVar); + + replace0(pos, bytecode, codeLength); + } + + /* <array type> $proceed(<dim> ..) + */ + static class ProceedForArray implements ProceedHandler { + CtClass arrayType; + int opcode; + int index, dimension; + + ProceedForArray(CtClass type, int op, int i, int dim) { + arrayType = type; + opcode = op; + index = i; + dimension = dim; + } + + public void doit(JvstCodeGen gen, Bytecode bytecode, ASTList args) + throws CompileError + { + int num = gen.getMethodArgsLength(args); + if (num != dimension) + throw new CompileError(Javac.proceedName + + "() with a wrong number of parameters"); + + gen.atMethodArgs(args, new int[num], + new int[num], new String[num]); + bytecode.addOpcode(opcode); + if (opcode == Opcode.ANEWARRAY) + bytecode.addIndex(index); + else if (opcode == Opcode.NEWARRAY) + bytecode.add(index); + else /* if (opcode == Opcode.MULTIANEWARRAY) */ { + bytecode.addIndex(index); + bytecode.add(dimension); + bytecode.growStack(1 - dimension); + } + + gen.setType(arrayType); + } + + public void setReturnType(JvstTypeChecker c, ASTList args) + throws CompileError + { + c.setType(arrayType); + } + } +} diff --git a/src/main/javassist/expr/NewExpr.java b/src/main/javassist/expr/NewExpr.java new file mode 100644 index 0000000..c2ab044 --- /dev/null +++ b/src/main/javassist/expr/NewExpr.java @@ -0,0 +1,247 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.expr; + +import javassist.*; +import javassist.bytecode.*; +import javassist.compiler.*; +import javassist.compiler.ast.ASTList; + +/** + * Object creation (<tt>new</tt> expression). + */ +public class NewExpr extends Expr { + String newTypeName; + int newPos; + + /** + * Undocumented constructor. Do not use; internal-use only. + */ + protected NewExpr(int pos, CodeIterator i, CtClass declaring, + MethodInfo m, String type, int np) { + super(pos, i, declaring, m); + newTypeName = type; + newPos = np; + } + + /* + * Not used + * + private int getNameAndType(ConstPool cp) { + int pos = currentPos; + int c = iterator.byteAt(pos); + int index = iterator.u16bitAt(pos + 1); + + if (c == INVOKEINTERFACE) + return cp.getInterfaceMethodrefNameAndType(index); + else + return cp.getMethodrefNameAndType(index); + } */ + + /** + * Returns the method or constructor containing the <tt>new</tt> + * expression represented by this object. + */ + public CtBehavior where() { return super.where(); } + + /** + * Returns the line number of the source line containing the + * <tt>new</tt> expression. + * + * @return -1 if this information is not available. + */ + public int getLineNumber() { + return super.getLineNumber(); + } + + /** + * Returns the source file containing the <tt>new</tt> expression. + * + * @return null if this information is not available. + */ + public String getFileName() { + return super.getFileName(); + } + + /** + * Returns the class of the created object. + */ + private CtClass getCtClass() throws NotFoundException { + return thisClass.getClassPool().get(newTypeName); + } + + /** + * Returns the class name of the created object. + */ + public String getClassName() { + return newTypeName; + } + + /** + * Get the signature of the constructor + * + * The signature is represented by a character string + * called method descriptor, which is defined in the JVM specification. + * + * @see javassist.CtBehavior#getSignature() + * @see javassist.bytecode.Descriptor + * @return the signature + */ + public String getSignature() { + ConstPool constPool = getConstPool(); + int methodIndex = iterator.u16bitAt(currentPos + 1); // constructor + return constPool.getMethodrefType(methodIndex); + } + + /** + * Returns the constructor called for creating the object. + */ + public CtConstructor getConstructor() throws NotFoundException { + ConstPool cp = getConstPool(); + int index = iterator.u16bitAt(currentPos + 1); + String desc = cp.getMethodrefType(index); + return getCtClass().getConstructor(desc); + } + + /** + * Returns the list of exceptions that the expression may throw. + * This list includes both the exceptions that the try-catch statements + * including the expression can catch and the exceptions that + * the throws declaration allows the method to throw. + */ + public CtClass[] mayThrow() { + return super.mayThrow(); + } + + /* + * Returns the parameter types of the constructor. + + public CtClass[] getParameterTypes() throws NotFoundException { + ConstPool cp = getConstPool(); + int index = iterator.u16bitAt(currentPos + 1); + String desc = cp.getMethodrefType(index); + return Descriptor.getParameterTypes(desc, thisClass.getClassPool()); + } + */ + + private int canReplace() throws CannotCompileException { + int op = iterator.byteAt(newPos + 3); + if (op == Opcode.DUP) + return 4; + else if (op == Opcode.DUP_X1 + && iterator.byteAt(newPos + 4) == Opcode.SWAP) + return 5; + else + return 3; // for Eclipse. The generated code may include no DUP. + // throw new CannotCompileException( + // "sorry, cannot edit NEW followed by no DUP"); + } + + /** + * Replaces the <tt>new</tt> expression with the bytecode derived from + * the given source text. + * + * <p>$0 is available but the value is null. + * + * @param statement a Java statement except try-catch. + */ + public void replace(String statement) throws CannotCompileException { + thisClass.getClassFile(); // to call checkModify(). + + final int bytecodeSize = 3; + int pos = newPos; + + int newIndex = iterator.u16bitAt(pos + 1); + + /* delete the preceding NEW and DUP (or DUP_X1, SWAP) instructions. + */ + int codeSize = canReplace(); + int end = pos + codeSize; + for (int i = pos; i < end; ++i) + iterator.writeByte(NOP, i); + + ConstPool constPool = getConstPool(); + pos = currentPos; + int methodIndex = iterator.u16bitAt(pos + 1); // constructor + + String signature = constPool.getMethodrefType(methodIndex); + + Javac jc = new Javac(thisClass); + ClassPool cp = thisClass.getClassPool(); + CodeAttribute ca = iterator.get(); + try { + CtClass[] params = Descriptor.getParameterTypes(signature, cp); + CtClass newType = cp.get(newTypeName); + int paramVar = ca.getMaxLocals(); + jc.recordParams(newTypeName, params, + true, paramVar, withinStatic()); + int retVar = jc.recordReturnType(newType, true); + jc.recordProceed(new ProceedForNew(newType, newIndex, + methodIndex)); + + /* Is $_ included in the source code? + */ + checkResultValue(newType, statement); + + Bytecode bytecode = jc.getBytecode(); + storeStack(params, true, paramVar, bytecode); + jc.recordLocalVariables(ca, pos); + + bytecode.addConstZero(newType); + bytecode.addStore(retVar, newType); // initialize $_ + + jc.compileStmnt(statement); + if (codeSize > 3) // if the original code includes DUP. + bytecode.addAload(retVar); + + replace0(pos, bytecode, bytecodeSize); + } + catch (CompileError e) { throw new CannotCompileException(e); } + catch (NotFoundException e) { throw new CannotCompileException(e); } + catch (BadBytecode e) { + throw new CannotCompileException("broken method"); + } + } + + static class ProceedForNew implements ProceedHandler { + CtClass newType; + int newIndex, methodIndex; + + ProceedForNew(CtClass nt, int ni, int mi) { + newType = nt; + newIndex = ni; + methodIndex = mi; + } + + public void doit(JvstCodeGen gen, Bytecode bytecode, ASTList args) + throws CompileError + { + bytecode.addOpcode(NEW); + bytecode.addIndex(newIndex); + bytecode.addOpcode(DUP); + gen.atMethodCallCore(newType, MethodInfo.nameInit, args, + false, true, -1, null); + gen.setType(newType); + } + + public void setReturnType(JvstTypeChecker c, ASTList args) + throws CompileError + { + c.atMethodCallCore(newType, MethodInfo.nameInit, args); + c.setType(newType); + } + } +} diff --git a/src/main/javassist/expr/package.html b/src/main/javassist/expr/package.html new file mode 100644 index 0000000..12a2c63 --- /dev/null +++ b/src/main/javassist/expr/package.html @@ -0,0 +1,8 @@ +<html> +<body> + +<p>This package contains the classes for modifying a method body. +See <code>ExprEditor</code> (expression editor) for more details. + +</body> +</html> diff --git a/src/main/javassist/package.html b/src/main/javassist/package.html new file mode 100644 index 0000000..f5b66b9 --- /dev/null +++ b/src/main/javassist/package.html @@ -0,0 +1,22 @@ +<html> +<body> +The Javassist Core API. + +<p>Javassist (<i>Java</i> programming <i>assist</i>ant) makes bytecode +engineering simple. It is a class library for editing +bytecode in Java; it enables Java programs to define a new class at +runtime and to modify a given class file when the JVM loads it. + +<p>The most significant class of this package is <code>CtClass</code>. +See the description of this class first. + +<p>To know the version number of this package, type the following command: + +<ul><pre> +java -jar javassist.jar +</pre></ul> + +<p>It prints the version number on the console. + +</body> +</html> diff --git a/src/main/javassist/runtime/Cflow.java b/src/main/javassist/runtime/Cflow.java new file mode 100644 index 0000000..641c63f --- /dev/null +++ b/src/main/javassist/runtime/Cflow.java @@ -0,0 +1,52 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.runtime; + +/** + * A support class for implementing <code>$cflow</code>. + * This support class is required at runtime + * only if <code>$cflow</code> is used. + * + * @see javassist.CtBehavior#useCflow(String) + */ +public class Cflow extends ThreadLocal { + private static class Depth { + private int depth; + Depth() { depth = 0; } + int get() { return depth; } + void inc() { ++depth; } + void dec() { --depth; } + } + + protected synchronized Object initialValue() { + return new Depth(); + } + + /** + * Increments the counter. + */ + public void enter() { ((Depth)get()).inc(); } + + /** + * Decrements the counter. + */ + public void exit() { ((Depth)get()).dec(); } + + /** + * Returns the value of the counter. + */ + public int value() { return ((Depth)get()).get(); } +} diff --git a/src/main/javassist/runtime/Desc.java b/src/main/javassist/runtime/Desc.java new file mode 100644 index 0000000..fa86a74 --- /dev/null +++ b/src/main/javassist/runtime/Desc.java @@ -0,0 +1,161 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.runtime; + +/** + * A support class for implementing <code>$sig</code> and + * <code>$type</code>. + * This support class is required at runtime + * only if <code>$sig</code> or <code>$type</code> is used. + */ +public class Desc { + + /** + * Specifies how a <code>java.lang.Class</code> object is loaded. + * + * <p>If true, it is loaded by: + * <ul><pre>Thread.currentThread().getContextClassLoader().loadClass()</pre></ul> + * <p>If false, it is loaded by <code>Class.forName()</code>. + * The default value is false. + */ + public static boolean useContextClassLoader = false; + + private static Class getClassObject(String name) + throws ClassNotFoundException + { + if (useContextClassLoader) + return Thread.currentThread().getContextClassLoader() + .loadClass(name); + else + return Class.forName(name); + } + + /** + * Interprets the given class name. + * It is used for implementing <code>$class</code>. + */ + public static Class getClazz(String name) { + try { + return getClassObject(name); + } + catch (ClassNotFoundException e) { + throw new RuntimeException( + "$class: internal error, could not find class '" + name + + "' (Desc.useContextClassLoader: " + + Boolean.toString(useContextClassLoader) + ")", e); + } + } + + /** + * Interprets the given type descriptor representing a method + * signature. It is used for implementing <code>$sig</code>. + */ + public static Class[] getParams(String desc) { + if (desc.charAt(0) != '(') + throw new RuntimeException("$sig: internal error"); + + return getType(desc, desc.length(), 1, 0); + } + + /** + * Interprets the given type descriptor. + * It is used for implementing <code>$type</code>. + */ + public static Class getType(String desc) { + Class[] result = getType(desc, desc.length(), 0, 0); + if (result == null || result.length != 1) + throw new RuntimeException("$type: internal error"); + + return result[0]; + } + + private static Class[] getType(String desc, int descLen, + int start, int num) { + Class clazz; + if (start >= descLen) + return new Class[num]; + + char c = desc.charAt(start); + switch (c) { + case 'Z' : + clazz = Boolean.TYPE; + break; + case 'C' : + clazz = Character.TYPE; + break; + case 'B' : + clazz = Byte.TYPE; + break; + case 'S' : + clazz = Short.TYPE; + break; + case 'I' : + clazz = Integer.TYPE; + break; + case 'J' : + clazz = Long.TYPE; + break; + case 'F' : + clazz = Float.TYPE; + break; + case 'D' : + clazz = Double.TYPE; + break; + case 'V' : + clazz = Void.TYPE; + break; + case 'L' : + case '[' : + return getClassType(desc, descLen, start, num); + default : + return new Class[num]; + } + + Class[] result = getType(desc, descLen, start + 1, num + 1); + result[num] = clazz; + return result; + } + + private static Class[] getClassType(String desc, int descLen, + int start, int num) { + int end = start; + while (desc.charAt(end) == '[') + ++end; + + if (desc.charAt(end) == 'L') { + end = desc.indexOf(';', end); + if (end < 0) + throw new IndexOutOfBoundsException("bad descriptor"); + } + + String cname; + if (desc.charAt(start) == 'L') + cname = desc.substring(start + 1, end); + else + cname = desc.substring(start, end + 1); + + Class[] result = getType(desc, descLen, end + 1, num + 1); + try { + result[num] = getClassObject(cname.replace('/', '.')); + } + catch (ClassNotFoundException e) { + // "new RuntimeException(e)" is not available in JDK 1.3. + throw new RuntimeException(e.getMessage()); + } + + return result; + } +} diff --git a/src/main/javassist/runtime/DotClass.java b/src/main/javassist/runtime/DotClass.java new file mode 100644 index 0000000..29db811 --- /dev/null +++ b/src/main/javassist/runtime/DotClass.java @@ -0,0 +1,28 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.runtime; + +/** + * A support class for implementing <code>.class</code> notation. + * This is required at runtime + * only if <code>.class</code> notation is used in source code given + * to the Javassist compiler. + */ +public class DotClass { + public static NoClassDefFoundError fail(ClassNotFoundException e) { + return new NoClassDefFoundError(e.getMessage()); + } +} diff --git a/src/main/javassist/runtime/Inner.java b/src/main/javassist/runtime/Inner.java new file mode 100644 index 0000000..ef96c50 --- /dev/null +++ b/src/main/javassist/runtime/Inner.java @@ -0,0 +1,24 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.runtime; + +/** + * A support class for compiling a method declared in an inner class. + * This support class is required at runtime + * only if the method calls a private constructor in the enclosing class. + */ +public class Inner { +} diff --git a/src/main/javassist/runtime/package.html b/src/main/javassist/runtime/package.html new file mode 100644 index 0000000..313648f --- /dev/null +++ b/src/main/javassist/runtime/package.html @@ -0,0 +1,12 @@ +<html> +<body> +Runtime support classes required by modified bytecode. + +<p>This package includes support classes that may be required by +classes modified with Javassist. Note that most of the modified +classes do not require these support classes. See the documentation +of every support class to know which kind of modification needs +a support class. + +</body> +</html> diff --git a/src/main/javassist/scopedpool/ScopedClassPool.java b/src/main/javassist/scopedpool/ScopedClassPool.java new file mode 100644 index 0000000..4833773 --- /dev/null +++ b/src/main/javassist/scopedpool/ScopedClassPool.java @@ -0,0 +1,308 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.scopedpool; + +import java.lang.ref.WeakReference; +import java.security.ProtectionDomain; +import java.util.Iterator; +import java.util.Map; +import javassist.CannotCompileException; +import javassist.ClassPool; +import javassist.CtClass; +import javassist.LoaderClassPath; +import javassist.NotFoundException; + +/** + * A scoped class pool. + * + * @author <a href="mailto:bill@jboss.org">Bill Burke</a> + * @author <a href="adrian@jboss.com">Adrian Brock</a> + * @author <a href="kabir.khan@jboss.com">Kabir Khan</a> + * @version $Revision: 1.8 $ + */ +public class ScopedClassPool extends ClassPool { + protected ScopedClassPoolRepository repository; + + protected WeakReference classLoader; + + protected LoaderClassPath classPath; + + protected SoftValueHashMap softcache = new SoftValueHashMap(); + + boolean isBootstrapCl = true; + + static { + ClassPool.doPruning = false; + ClassPool.releaseUnmodifiedClassFile = false; + } + + /** + * Create a new ScopedClassPool. + * + * @param cl + * the classloader + * @param src + * the original class pool + * @param repository + * the repository + *@deprecated + */ + protected ScopedClassPool(ClassLoader cl, ClassPool src, + ScopedClassPoolRepository repository) { + this(cl, src, repository, false); + } + + /** + * Create a new ScopedClassPool. + * + * @param cl + * the classloader + * @param src + * the original class pool + * @param repository + * the repository + * @param isTemp + * Whether this is a temporary pool used to resolve references + */ + protected ScopedClassPool(ClassLoader cl, ClassPool src, ScopedClassPoolRepository repository, boolean isTemp) + { + super(src); + this.repository = repository; + this.classLoader = new WeakReference(cl); + if (cl != null) { + classPath = new LoaderClassPath(cl); + this.insertClassPath(classPath); + } + childFirstLookup = true; + if (!isTemp && cl == null) + { + isBootstrapCl = true; + } + } + + /** + * Get the class loader + * + * @return the class loader + */ + public ClassLoader getClassLoader() { + ClassLoader cl = getClassLoader0(); + if (cl == null && !isBootstrapCl) + { + throw new IllegalStateException( + "ClassLoader has been garbage collected"); + } + return cl; + } + + protected ClassLoader getClassLoader0() { + return (ClassLoader)classLoader.get(); + } + + /** + * Close the class pool + */ + public void close() { + this.removeClassPath(classPath); + classPath.close(); + classes.clear(); + softcache.clear(); + } + + /** + * Flush a class + * + * @param classname + * the class to flush + */ + public synchronized void flushClass(String classname) { + classes.remove(classname); + softcache.remove(classname); + } + + /** + * Soften a class + * + * @param clazz + * the class + */ + public synchronized void soften(CtClass clazz) { + if (repository.isPrune()) + clazz.prune(); + classes.remove(clazz.getName()); + softcache.put(clazz.getName(), clazz); + } + + /** + * Whether the classloader is loader + * + * @return false always + */ + public boolean isUnloadedClassLoader() { + return false; + } + + /** + * Get the cached class + * + * @param classname + * the class name + * @return the class + */ + protected CtClass getCached(String classname) { + CtClass clazz = getCachedLocally(classname); + if (clazz == null) { + boolean isLocal = false; + + ClassLoader dcl = getClassLoader0(); + if (dcl != null) { + final int lastIndex = classname.lastIndexOf('$'); + String classResourceName = null; + if (lastIndex < 0) { + classResourceName = classname.replaceAll("[\\.]", "/") + + ".class"; + } + else { + classResourceName = classname.substring(0, lastIndex) + .replaceAll("[\\.]", "/") + + classname.substring(lastIndex) + ".class"; + } + + isLocal = dcl.getResource(classResourceName) != null; + } + + if (!isLocal) { + Map registeredCLs = repository.getRegisteredCLs(); + synchronized (registeredCLs) { + Iterator it = registeredCLs.values().iterator(); + while (it.hasNext()) { + ScopedClassPool pool = (ScopedClassPool)it.next(); + if (pool.isUnloadedClassLoader()) { + repository.unregisterClassLoader(pool + .getClassLoader()); + continue; + } + + clazz = pool.getCachedLocally(classname); + if (clazz != null) { + return clazz; + } + } + } + } + } + // *NOTE* NEED TO TEST WHEN SUPERCLASS IS IN ANOTHER UCL!!!!!! + return clazz; + } + + /** + * Cache a class + * + * @param classname + * the class name + * @param c + * the ctClass + * @param dynamic + * whether the class is dynamically generated + */ + protected void cacheCtClass(String classname, CtClass c, boolean dynamic) { + if (dynamic) { + super.cacheCtClass(classname, c, dynamic); + } + else { + if (repository.isPrune()) + c.prune(); + softcache.put(classname, c); + } + } + + /** + * Lock a class into the cache + * + * @param c + * the class + */ + public void lockInCache(CtClass c) { + super.cacheCtClass(c.getName(), c, false); + } + + /** + * Whether the class is cached in this pooled + * + * @param classname + * the class name + * @return the cached class + */ + protected CtClass getCachedLocally(String classname) { + CtClass cached = (CtClass)classes.get(classname); + if (cached != null) + return cached; + synchronized (softcache) { + return (CtClass)softcache.get(classname); + } + } + + /** + * Get any local copy of the class + * + * @param classname + * the class name + * @return the class + * @throws NotFoundException + * when the class is not found + */ + public synchronized CtClass getLocally(String classname) + throws NotFoundException { + softcache.remove(classname); + CtClass clazz = (CtClass)classes.get(classname); + if (clazz == null) { + clazz = createCtClass(classname, true); + if (clazz == null) + throw new NotFoundException(classname); + super.cacheCtClass(classname, clazz, false); + } + + return clazz; + } + + /** + * Convert a javassist class to a java class + * + * @param ct + * the javassist class + * @param loader + * the loader + * @throws CannotCompileException + * for any error + */ + public Class toClass(CtClass ct, ClassLoader loader, ProtectionDomain domain) + throws CannotCompileException { + // We need to pass up the classloader stored in this pool, as the + // default implementation uses the Thread context cl. + // In the case of JSP's in Tomcat, + // org.apache.jasper.servlet.JasperLoader will be stored here, while + // it's parent + // org.jboss.web.tomcat.tc5.WebCtxLoader$ENCLoader is used as the Thread + // context cl. The invocation class needs to + // be generated in the JasperLoader classloader since in the case of + // method invocations, the package name will be + // the same as for the class generated from the jsp, i.e. + // org.apache.jsp. For classes belonging to org.apache.jsp, + // JasperLoader does NOT delegate to its parent if it cannot find them. + lockInCache(ct); + return super.toClass(ct, getClassLoader0(), domain); + } +} diff --git a/src/main/javassist/scopedpool/ScopedClassPoolFactory.java b/src/main/javassist/scopedpool/ScopedClassPoolFactory.java new file mode 100644 index 0000000..1a998a9 --- /dev/null +++ b/src/main/javassist/scopedpool/ScopedClassPoolFactory.java @@ -0,0 +1,38 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.scopedpool; + +import javassist.ClassPool; + +/** + * A factory interface. + * + * @author <a href="kabir.khan@jboss.com">Kabir Khan</a> + * @version $Revision: 1.4 $ + */ +public interface ScopedClassPoolFactory { + /** + * Makes an instance. + */ + ScopedClassPool create(ClassLoader cl, ClassPool src, + ScopedClassPoolRepository repository); + + /** + * Makes an instance. + */ + ScopedClassPool create(ClassPool src, + ScopedClassPoolRepository repository); +} diff --git a/src/main/javassist/scopedpool/ScopedClassPoolFactoryImpl.java b/src/main/javassist/scopedpool/ScopedClassPoolFactoryImpl.java new file mode 100644 index 0000000..b8e66d6 --- /dev/null +++ b/src/main/javassist/scopedpool/ScopedClassPoolFactoryImpl.java @@ -0,0 +1,42 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.scopedpool; + +import javassist.ClassPool; + +/** + * An implementation of factory. + * + * @author <a href="kabir.khan@jboss.com">Kabir Khan</a> + * @version $Revision: 1.5 $ + */ +public class ScopedClassPoolFactoryImpl implements ScopedClassPoolFactory { + /** + * Makes an instance. + */ + public ScopedClassPool create(ClassLoader cl, ClassPool src, + ScopedClassPoolRepository repository) { + return new ScopedClassPool(cl, src, repository, false); + } + + /** + * Makes an instance. + */ + public ScopedClassPool create(ClassPool src, + ScopedClassPoolRepository repository) { + return new ScopedClassPool(null, src, repository, true); + } +} diff --git a/src/main/javassist/scopedpool/ScopedClassPoolRepository.java b/src/main/javassist/scopedpool/ScopedClassPoolRepository.java new file mode 100644 index 0000000..7ebf8f8 --- /dev/null +++ b/src/main/javassist/scopedpool/ScopedClassPoolRepository.java @@ -0,0 +1,97 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.scopedpool; + +import java.util.Map; + +import javassist.ClassPool; + +/** + * An interface to <code>ScopedClassPoolRepositoryImpl</code>. + * + * @author <a href="kabir.khan@jboss.com">Kabir Khan</a> + * @version $Revision: 1.4 $ + */ +public interface ScopedClassPoolRepository { + /** + * Records a factory. + */ + void setClassPoolFactory(ScopedClassPoolFactory factory); + + /** + * Obtains the recorded factory. + */ + ScopedClassPoolFactory getClassPoolFactory(); + + /** + * Returns whether or not the class pool is pruned. + * + * @return the prune. + */ + boolean isPrune(); + + /** + * Sets the prune flag. + * + * @param prune a new value. + */ + void setPrune(boolean prune); + + /** + * Create a scoped classpool. + * + * @param cl the classloader. + * @param src the original classpool. + * @return the classpool. + */ + ScopedClassPool createScopedClassPool(ClassLoader cl, ClassPool src); + + /** + * Finds a scoped classpool registered under the passed in classloader. + * + * @param cl the classloader. + * @return the classpool. + */ + ClassPool findClassPool(ClassLoader cl); + + /** + * Register a classloader. + * + * @param ucl the classloader. + * @return the classpool. + */ + ClassPool registerClassLoader(ClassLoader ucl); + + /** + * Get the registered classloaders. + * + * @return the registered classloaders. + */ + Map getRegisteredCLs(); + + /** + * This method will check to see if a register classloader has been + * undeployed (as in JBoss). + */ + void clearUnregisteredClassLoaders(); + + /** + * Unregisters a classpool and unregisters its classloader. + * + * @param cl the classloader the pool is stored under. + */ + void unregisterClassLoader(ClassLoader cl); +} diff --git a/src/main/javassist/scopedpool/ScopedClassPoolRepositoryImpl.java b/src/main/javassist/scopedpool/ScopedClassPoolRepositoryImpl.java new file mode 100644 index 0000000..2245c7d --- /dev/null +++ b/src/main/javassist/scopedpool/ScopedClassPoolRepositoryImpl.java @@ -0,0 +1,187 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.scopedpool; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.Map; +import java.util.WeakHashMap; + +import javassist.ClassPool; +import javassist.LoaderClassPath; + +/** + * An implementation of <code>ScopedClassPoolRepository</code>. + * It is an singleton. + * + * @author <a href="kabir.khan@jboss.com">Kabir Khan</a> + * @version $Revision: 1.4 $ + */ +public class ScopedClassPoolRepositoryImpl implements ScopedClassPoolRepository { + /** The instance */ + private static final ScopedClassPoolRepositoryImpl instance = new ScopedClassPoolRepositoryImpl(); + + /** Whether to prune */ + private boolean prune = true; + + /** Whether to prune when added to the classpool's cache */ + boolean pruneWhenCached; + + /** The registered classloaders */ + protected Map registeredCLs = Collections + .synchronizedMap(new WeakHashMap()); + + /** The default class pool */ + protected ClassPool classpool; + + /** The factory for creating class pools */ + protected ScopedClassPoolFactory factory = new ScopedClassPoolFactoryImpl(); + + /** + * Get the instance. + * + * @return the instance. + */ + public static ScopedClassPoolRepository getInstance() { + return instance; + } + + /** + * Singleton. + */ + private ScopedClassPoolRepositoryImpl() { + classpool = ClassPool.getDefault(); + // FIXME This doesn't look correct + ClassLoader cl = Thread.currentThread().getContextClassLoader(); + classpool.insertClassPath(new LoaderClassPath(cl)); + } + + /** + * Returns the value of the prune attribute. + * + * @return the prune. + */ + public boolean isPrune() { + return prune; + } + + /** + * Set the prune attribute. + * + * @param prune a new value. + */ + public void setPrune(boolean prune) { + this.prune = prune; + } + + /** + * Create a scoped classpool. + * + * @param cl the classloader. + * @param src the original classpool. + * @return the classpool + */ + public ScopedClassPool createScopedClassPool(ClassLoader cl, ClassPool src) { + return factory.create(cl, src, this); + } + + public ClassPool findClassPool(ClassLoader cl) { + if (cl == null) + return registerClassLoader(ClassLoader.getSystemClassLoader()); + + return registerClassLoader(cl); + } + + /** + * Register a classloader. + * + * @param ucl the classloader. + * @return the classpool + */ + public ClassPool registerClassLoader(ClassLoader ucl) { + synchronized (registeredCLs) { + // FIXME: Probably want to take this method out later + // so that AOP framework can be independent of JMX + // This is in here so that we can remove a UCL from the ClassPool as + // a + // ClassPool.classpath + if (registeredCLs.containsKey(ucl)) { + return (ClassPool)registeredCLs.get(ucl); + } + ScopedClassPool pool = createScopedClassPool(ucl, classpool); + registeredCLs.put(ucl, pool); + return pool; + } + } + + /** + * Get the registered classloaders. + */ + public Map getRegisteredCLs() { + clearUnregisteredClassLoaders(); + return registeredCLs; + } + + /** + * This method will check to see if a register classloader has been + * undeployed (as in JBoss) + */ + public void clearUnregisteredClassLoaders() { + ArrayList toUnregister = null; + synchronized (registeredCLs) { + Iterator it = registeredCLs.values().iterator(); + while (it.hasNext()) { + ScopedClassPool pool = (ScopedClassPool)it.next(); + if (pool.isUnloadedClassLoader()) { + it.remove(); + ClassLoader cl = pool.getClassLoader(); + if (cl != null) { + if (toUnregister == null) { + toUnregister = new ArrayList(); + } + toUnregister.add(cl); + } + } + } + if (toUnregister != null) { + for (int i = 0; i < toUnregister.size(); i++) { + unregisterClassLoader((ClassLoader)toUnregister.get(i)); + } + } + } + } + + public void unregisterClassLoader(ClassLoader cl) { + synchronized (registeredCLs) { + ScopedClassPool pool = (ScopedClassPool)registeredCLs.remove(cl); + if (pool != null) + pool.close(); + } + } + + public void insertDelegate(ScopedClassPoolRepository delegate) { + // Noop - this is the end + } + + public void setClassPoolFactory(ScopedClassPoolFactory factory) { + this.factory = factory; + } + + public ScopedClassPoolFactory getClassPoolFactory() { + return factory; + } +} diff --git a/src/main/javassist/scopedpool/SoftValueHashMap.java b/src/main/javassist/scopedpool/SoftValueHashMap.java new file mode 100644 index 0000000..6a73d9e --- /dev/null +++ b/src/main/javassist/scopedpool/SoftValueHashMap.java @@ -0,0 +1,232 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.scopedpool; + +import java.lang.ref.ReferenceQueue; +import java.lang.ref.SoftReference; +import java.util.AbstractMap; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * This Map will remove entries when the value in the map has been cleaned from + * garbage collection + * + * @version <tt>$Revision: 1.4 $</tt> + * @author <a href="mailto:bill@jboss.org">Bill Burke</a> + */ +public class SoftValueHashMap extends AbstractMap implements Map { + private static class SoftValueRef extends SoftReference { + public Object key; + + private SoftValueRef(Object key, Object val, ReferenceQueue q) { + super(val, q); + this.key = key; + } + + private static SoftValueRef create(Object key, Object val, + ReferenceQueue q) { + if (val == null) + return null; + else + return new SoftValueRef(key, val, q); + } + + } + + /** + * Returns a set of the mappings contained in this hash table. + */ + public Set entrySet() { + processQueue(); + return hash.entrySet(); + } + + /* Hash table mapping WeakKeys to values */ + private Map hash; + + /* Reference queue for cleared WeakKeys */ + private ReferenceQueue queue = new ReferenceQueue(); + + /* + * Remove all invalidated entries from the map, that is, remove all entries + * whose values have been discarded. + */ + private void processQueue() { + SoftValueRef ref; + while ((ref = (SoftValueRef)queue.poll()) != null) { + if (ref == (SoftValueRef)hash.get(ref.key)) { + // only remove if it is the *exact* same WeakValueRef + // + hash.remove(ref.key); + } + } + } + + /* -- Constructors -- */ + + /** + * Constructs a new, empty <code>WeakHashMap</code> with the given initial + * capacity and the given load factor. + * + * @param initialCapacity + * The initial capacity of the <code>WeakHashMap</code> + * + * @param loadFactor + * The load factor of the <code>WeakHashMap</code> + * + * @throws IllegalArgumentException + * If the initial capacity is less than zero, or if the load + * factor is nonpositive + */ + public SoftValueHashMap(int initialCapacity, float loadFactor) { + hash = new HashMap(initialCapacity, loadFactor); + } + + /** + * Constructs a new, empty <code>WeakHashMap</code> with the given initial + * capacity and the default load factor, which is <code>0.75</code>. + * + * @param initialCapacity + * The initial capacity of the <code>WeakHashMap</code> + * + * @throws IllegalArgumentException + * If the initial capacity is less than zero + */ + public SoftValueHashMap(int initialCapacity) { + hash = new HashMap(initialCapacity); + } + + /** + * Constructs a new, empty <code>WeakHashMap</code> with the default + * initial capacity and the default load factor, which is <code>0.75</code>. + */ + public SoftValueHashMap() { + hash = new HashMap(); + } + + /** + * Constructs a new <code>WeakHashMap</code> with the same mappings as the + * specified <tt>Map</tt>. The <code>WeakHashMap</code> is created with + * an initial capacity of twice the number of mappings in the specified map + * or 11 (whichever is greater), and a default load factor, which is + * <tt>0.75</tt>. + * + * @param t the map whose mappings are to be placed in this map. + */ + public SoftValueHashMap(Map t) { + this(Math.max(2 * t.size(), 11), 0.75f); + putAll(t); + } + + /* -- Simple queries -- */ + + /** + * Returns the number of key-value mappings in this map. <strong>Note:</strong> + * <em>In contrast with most implementations of the + * <code>Map</code> interface, the time required by this operation is + * linear in the size of the map.</em> + */ + public int size() { + processQueue(); + return hash.size(); + } + + /** + * Returns <code>true</code> if this map contains no key-value mappings. + */ + public boolean isEmpty() { + processQueue(); + return hash.isEmpty(); + } + + /** + * Returns <code>true</code> if this map contains a mapping for the + * specified key. + * + * @param key + * The key whose presence in this map is to be tested. + */ + public boolean containsKey(Object key) { + processQueue(); + return hash.containsKey(key); + } + + /* -- Lookup and modification operations -- */ + + /** + * Returns the value to which this map maps the specified <code>key</code>. + * If this map does not contain a value for this key, then return + * <code>null</code>. + * + * @param key + * The key whose associated value, if any, is to be returned. + */ + public Object get(Object key) { + processQueue(); + SoftReference ref = (SoftReference)hash.get(key); + if (ref != null) + return ref.get(); + return null; + } + + /** + * Updates this map so that the given <code>key</code> maps to the given + * <code>value</code>. If the map previously contained a mapping for + * <code>key</code> then that mapping is replaced and the previous value + * is returned. + * + * @param key + * The key that is to be mapped to the given <code>value</code> + * @param value + * The value to which the given <code>key</code> is to be + * mapped + * + * @return The previous value to which this key was mapped, or + * <code>null</code> if if there was no mapping for the key + */ + public Object put(Object key, Object value) { + processQueue(); + Object rtn = hash.put(key, SoftValueRef.create(key, value, queue)); + if (rtn != null) + rtn = ((SoftReference)rtn).get(); + return rtn; + } + + /** + * Removes the mapping for the given <code>key</code> from this map, if + * present. + * + * @param key + * The key whose mapping is to be removed. + * + * @return The value to which this key was mapped, or <code>null</code> if + * there was no mapping for the key. + */ + public Object remove(Object key) { + processQueue(); + return hash.remove(key); + } + + /** + * Removes all mappings from this map. + */ + public void clear() { + processQueue(); + hash.clear(); + } +} diff --git a/src/main/javassist/scopedpool/package.html b/src/main/javassist/scopedpool/package.html new file mode 100644 index 0000000..946e5e1 --- /dev/null +++ b/src/main/javassist/scopedpool/package.html @@ -0,0 +1,7 @@ +<html> +<body> +<p>A custom class pool for several JBoss products. +It is not part of Javassist. +</p> +</body> +</html> diff --git a/src/main/javassist/tools/Dump.java b/src/main/javassist/tools/Dump.java new file mode 100644 index 0000000..2f064a8 --- /dev/null +++ b/src/main/javassist/tools/Dump.java @@ -0,0 +1,57 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.tools; + +import java.io.*; +import javassist.bytecode.ClassFile; +import javassist.bytecode.ClassFilePrinter; + +/** + * Dump is a tool for viewing the class definition in the given + * class file. Unlike the JDK javap tool, Dump works even if + * the class file is broken. + * + * <p>For example, + * <ul><pre>% java javassist.tools.Dump foo.class</pre></ul> + * + * <p>prints the contents of the constant pool and the list of methods + * and fields. + */ +public class Dump { + private Dump() {} + + /** + * Main method. + * + * @param args <code>args[0]</code> is the class file name. + */ + public static void main(String[] args) throws Exception { + if (args.length != 1) { + System.err.println("Usage: java Dump <class file name>"); + return; + } + + DataInputStream in = new DataInputStream( + new FileInputStream(args[0])); + ClassFile w = new ClassFile(in); + PrintWriter out = new PrintWriter(System.out, true); + out.println("*** constant pool ***"); + w.getConstPool().print(out); + out.println(); + out.println("*** members ***"); + ClassFilePrinter.print(w, out); + } +} diff --git a/src/main/javassist/tools/framedump.java b/src/main/javassist/tools/framedump.java new file mode 100644 index 0000000..0a30fb1 --- /dev/null +++ b/src/main/javassist/tools/framedump.java @@ -0,0 +1,47 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba, and others. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ +package javassist.tools; + +import javassist.ClassPool; +import javassist.CtClass; +import javassist.bytecode.analysis.FramePrinter; + +/** + * framedump is a tool for viewing a merged combination of the instructions and frame state + * of all methods in a class. + * + * <p>For example, + * <ul><pre>% java javassist.tools.framedump foo.class</pre></ul> + */ +public class framedump { + private framedump() {} + + /** + * Main method. + * + * @param args <code>args[0]</code> is the class file name. + */ + public static void main(String[] args) throws Exception { + if (args.length != 1) { + System.err.println("Usage: java javassist.tools.framedump <class file name>"); + return; + } + + ClassPool pool = ClassPool.getDefault(); + CtClass clazz = pool.get(args[0]); + System.out.println("Frame Dump of " + clazz.getName() + ":"); + FramePrinter.print(clazz, System.out); + } +} diff --git a/src/main/javassist/tools/package.html b/src/main/javassist/tools/package.html new file mode 100644 index 0000000..bee6208 --- /dev/null +++ b/src/main/javassist/tools/package.html @@ -0,0 +1,6 @@ +<html> +<body> +Covenient tools. + +</body> +</html> diff --git a/src/main/javassist/tools/reflect/CannotCreateException.java b/src/main/javassist/tools/reflect/CannotCreateException.java new file mode 100644 index 0000000..75ffe0c --- /dev/null +++ b/src/main/javassist/tools/reflect/CannotCreateException.java @@ -0,0 +1,29 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.tools.reflect; + +/** + * Signals that <code>ClassMetaobject.newInstance()</code> fails. + */ +public class CannotCreateException extends Exception { + public CannotCreateException(String s) { + super(s); + } + + public CannotCreateException(Exception e) { + super("by " + e.toString()); + } +} diff --git a/src/main/javassist/tools/reflect/CannotInvokeException.java b/src/main/javassist/tools/reflect/CannotInvokeException.java new file mode 100644 index 0000000..8c063d7 --- /dev/null +++ b/src/main/javassist/tools/reflect/CannotInvokeException.java @@ -0,0 +1,68 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.tools.reflect; + +import java.lang.reflect.InvocationTargetException; +import java.lang.IllegalAccessException; + +/** + * Thrown when method invocation using the reflection API has thrown + * an exception. + * + * @see javassist.tools.reflect.Metaobject#trapMethodcall(int, Object[]) + * @see javassist.tools.reflect.ClassMetaobject#trapMethodcall(int, Object[]) + * @see javassist.tools.reflect.ClassMetaobject#invoke(Object, int, Object[]) + */ +public class CannotInvokeException extends RuntimeException { + + private Throwable err = null; + + /** + * Returns the cause of this exception. It may return null. + */ + public Throwable getReason() { return err; } + + /** + * Constructs a CannotInvokeException with an error message. + */ + public CannotInvokeException(String reason) { + super(reason); + } + + /** + * Constructs a CannotInvokeException with an InvocationTargetException. + */ + public CannotInvokeException(InvocationTargetException e) { + super("by " + e.getTargetException().toString()); + err = e.getTargetException(); + } + + /** + * Constructs a CannotInvokeException with an IllegalAccessException. + */ + public CannotInvokeException(IllegalAccessException e) { + super("by " + e.toString()); + err = e; + } + + /** + * Constructs a CannotInvokeException with an ClassNotFoundException. + */ + public CannotInvokeException(ClassNotFoundException e) { + super("by " + e.toString()); + err = e; + } +} diff --git a/src/main/javassist/tools/reflect/CannotReflectException.java b/src/main/javassist/tools/reflect/CannotReflectException.java new file mode 100644 index 0000000..0af2892 --- /dev/null +++ b/src/main/javassist/tools/reflect/CannotReflectException.java @@ -0,0 +1,34 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.tools.reflect; + +import javassist.CannotCompileException; + +/** + * Thrown by <code>makeReflective()</code> in <code>Reflection</code> + * when there is an attempt to reflect + * a class that is either an interface or a subclass of + * either ClassMetaobject or Metaobject. + * + * @author Brett Randall + * @see javassist.tools.reflect.Reflection#makeReflective(CtClass,CtClass,CtClass) + * @see javassist.CannotCompileException + */ +public class CannotReflectException extends CannotCompileException { + public CannotReflectException(String msg) { + super(msg); + } +} diff --git a/src/main/javassist/tools/reflect/ClassMetaobject.java b/src/main/javassist/tools/reflect/ClassMetaobject.java new file mode 100644 index 0000000..208d104 --- /dev/null +++ b/src/main/javassist/tools/reflect/ClassMetaobject.java @@ -0,0 +1,369 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.tools.reflect; + +import java.lang.reflect.*; +import java.util.Arrays; +import java.io.Serializable; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + +/** + * A runtime class metaobject. + * + * <p>A <code>ClassMetaobject</code> is created for every + * class of reflective objects. It can be used to hold values + * shared among the reflective objects of the same class. + * + * <p>To obtain a class metaobject, calls <code>_getClass()</code> + * on a reflective object. For example, + * + * <ul><pre>ClassMetaobject cm = ((Metalevel)reflectiveObject)._getClass(); + * </pre></ul> + * + * @see javassist.tools.reflect.Metaobject + * @see javassist.tools.reflect.Metalevel + */ +public class ClassMetaobject implements Serializable { + /** + * The base-level methods controlled by a metaobject + * are renamed so that they begin with + * <code>methodPrefix "_m_"</code>. + */ + static final String methodPrefix = "_m_"; + static final int methodPrefixLen = 3; + + private Class javaClass; + private Constructor[] constructors; + private Method[] methods; + + /** + * Specifies how a <code>java.lang.Class</code> object is loaded. + * + * <p>If true, it is loaded by: + * <ul><pre>Thread.currentThread().getContextClassLoader().loadClass()</pre></ul> + * <p>If false, it is loaded by <code>Class.forName()</code>. + * The default value is false. + */ + public static boolean useContextClassLoader = false; + + /** + * Constructs a <code>ClassMetaobject</code>. + * + * @param params <code>params[0]</code> is the name of the class + * of the reflective objects. + */ + public ClassMetaobject(String[] params) + { + try { + javaClass = getClassObject(params[0]); + } + catch (ClassNotFoundException e) { + throw new RuntimeException("not found: " + params[0] + + ", useContextClassLoader: " + + Boolean.toString(useContextClassLoader), e); + } + + constructors = javaClass.getConstructors(); + methods = null; + } + + private void writeObject(ObjectOutputStream out) throws IOException { + out.writeUTF(javaClass.getName()); + } + + private void readObject(ObjectInputStream in) + throws IOException, ClassNotFoundException + { + javaClass = getClassObject(in.readUTF()); + constructors = javaClass.getConstructors(); + methods = null; + } + + private Class getClassObject(String name) throws ClassNotFoundException { + if (useContextClassLoader) + return Thread.currentThread().getContextClassLoader() + .loadClass(name); + else + return Class.forName(name); + } + + /** + * Obtains the <code>java.lang.Class</code> representing this class. + */ + public final Class getJavaClass() { + return javaClass; + } + + /** + * Obtains the name of this class. + */ + public final String getName() { + return javaClass.getName(); + } + + /** + * Returns true if <code>obj</code> is an instance of this class. + */ + public final boolean isInstance(Object obj) { + return javaClass.isInstance(obj); + } + + /** + * Creates a new instance of the class. + * + * @param args the arguments passed to the constructor. + */ + public final Object newInstance(Object[] args) + throws CannotCreateException + { + int n = constructors.length; + for (int i = 0; i < n; ++i) { + try { + return constructors[i].newInstance(args); + } + catch (IllegalArgumentException e) { + // try again + } + catch (InstantiationException e) { + throw new CannotCreateException(e); + } + catch (IllegalAccessException e) { + throw new CannotCreateException(e); + } + catch (InvocationTargetException e) { + throw new CannotCreateException(e); + } + } + + throw new CannotCreateException("no constructor matches"); + } + + /** + * Is invoked when <code>static</code> fields of the base-level + * class are read and the runtime system intercepts it. + * This method simply returns the value of the field. + * + * <p>Every subclass of this class should redefine this method. + */ + public Object trapFieldRead(String name) { + Class jc = getJavaClass(); + try { + return jc.getField(name).get(null); + } + catch (NoSuchFieldException e) { + throw new RuntimeException(e.toString()); + } + catch (IllegalAccessException e) { + throw new RuntimeException(e.toString()); + } + } + + /** + * Is invoked when <code>static</code> fields of the base-level + * class are modified and the runtime system intercepts it. + * This method simply sets the field to the given value. + * + * <p>Every subclass of this class should redefine this method. + */ + public void trapFieldWrite(String name, Object value) { + Class jc = getJavaClass(); + try { + jc.getField(name).set(null, value); + } + catch (NoSuchFieldException e) { + throw new RuntimeException(e.toString()); + } + catch (IllegalAccessException e) { + throw new RuntimeException(e.toString()); + } + } + + /** + * Invokes a method whose name begins with + * <code>methodPrefix "_m_"</code> and the identifier. + * + * @exception CannotInvokeException if the invocation fails. + */ + static public Object invoke(Object target, int identifier, Object[] args) + throws Throwable + { + Method[] allmethods = target.getClass().getMethods(); + int n = allmethods.length; + String head = methodPrefix + identifier; + for (int i = 0; i < n; ++i) + if (allmethods[i].getName().startsWith(head)) { + try { + return allmethods[i].invoke(target, args); + } catch (java.lang.reflect.InvocationTargetException e) { + throw e.getTargetException(); + } catch (java.lang.IllegalAccessException e) { + throw new CannotInvokeException(e); + } + } + + throw new CannotInvokeException("cannot find a method"); + } + + /** + * Is invoked when <code>static</code> methods of the base-level + * class are called and the runtime system intercepts it. + * This method simply executes the intercepted method invocation + * with the original parameters and returns the resulting value. + * + * <p>Every subclass of this class should redefine this method. + */ + public Object trapMethodcall(int identifier, Object[] args) + throws Throwable + { + try { + Method[] m = getReflectiveMethods(); + return m[identifier].invoke(null, args); + } + catch (java.lang.reflect.InvocationTargetException e) { + throw e.getTargetException(); + } + catch (java.lang.IllegalAccessException e) { + throw new CannotInvokeException(e); + } + } + + /** + * Returns an array of the methods defined on the given reflective + * object. This method is for the internal use only. + */ + public final Method[] getReflectiveMethods() { + if (methods != null) + return methods; + + Class baseclass = getJavaClass(); + Method[] allmethods = baseclass.getDeclaredMethods(); + int n = allmethods.length; + int[] index = new int[n]; + int max = 0; + for (int i = 0; i < n; ++i) { + Method m = allmethods[i]; + String mname = m.getName(); + if (mname.startsWith(methodPrefix)) { + int k = 0; + for (int j = methodPrefixLen;; ++j) { + char c = mname.charAt(j); + if ('0' <= c && c <= '9') + k = k * 10 + c - '0'; + else + break; + } + + index[i] = ++k; + if (k > max) + max = k; + } + } + + methods = new Method[max]; + for (int i = 0; i < n; ++i) + if (index[i] > 0) + methods[index[i] - 1] = allmethods[i]; + + return methods; + } + + /** + * Returns the <code>java.lang.reflect.Method</code> object representing + * the method specified by <code>identifier</code>. + * + * <p>Note that the actual method returned will be have an altered, + * reflective name i.e. <code>_m_2_..</code>. + * + * @param identifier the identifier index + * given to <code>trapMethodcall()</code> etc. + * @see Metaobject#trapMethodcall(int,Object[]) + * @see #trapMethodcall(int,Object[]) + */ + public final Method getMethod(int identifier) { + return getReflectiveMethods()[identifier]; + } + + /** + * Returns the name of the method specified + * by <code>identifier</code>. + */ + public final String getMethodName(int identifier) { + String mname = getReflectiveMethods()[identifier].getName(); + int j = ClassMetaobject.methodPrefixLen; + for (;;) { + char c = mname.charAt(j++); + if (c < '0' || '9' < c) + break; + } + + return mname.substring(j); + } + + /** + * Returns an array of <code>Class</code> objects representing the + * formal parameter types of the method specified + * by <code>identifier</code>. + */ + public final Class[] getParameterTypes(int identifier) { + return getReflectiveMethods()[identifier].getParameterTypes(); + } + + /** + * Returns a <code>Class</code> objects representing the + * return type of the method specified by <code>identifier</code>. + */ + public final Class getReturnType(int identifier) { + return getReflectiveMethods()[identifier].getReturnType(); + } + + /** + * Returns the identifier index of the method, as identified by its + * original name. + * + * <p>This method is useful, in conjuction with + * <link>ClassMetaobject#getMethod()</link>, to obtain a quick reference + * to the original method in the reflected class (i.e. not the proxy + * method), using the original name of the method. + * + * <p>Written by Brett Randall and Shigeru Chiba. + * + * @param originalName The original name of the reflected method + * @param argTypes array of Class specifying the method signature + * @return the identifier index of the original method + * @throws NoSuchMethodException if the method does not exist + * + * @see ClassMetaobject#getMethod(int) + */ + public final int getMethodIndex(String originalName, Class[] argTypes) + throws NoSuchMethodException + { + Method[] mthds = getReflectiveMethods(); + for (int i = 0; i < mthds.length; i++) { + if (mthds[i] == null) + continue; + + // check name and parameter types match + if (getMethodName(i).equals(originalName) + && Arrays.equals(argTypes, mthds[i].getParameterTypes())) + return i; + } + + throw new NoSuchMethodException("Method " + originalName + + " not found"); + } +} diff --git a/src/main/javassist/tools/reflect/Compiler.java b/src/main/javassist/tools/reflect/Compiler.java new file mode 100644 index 0000000..5375584 --- /dev/null +++ b/src/main/javassist/tools/reflect/Compiler.java @@ -0,0 +1,162 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.tools.reflect; + +import javassist.CtClass; +import javassist.ClassPool; +import java.io.PrintStream; + +class CompiledClass { + public String classname; + public String metaobject; + public String classobject; +} + +/** + * A bytecode translator for reflection. + * + * <p>This translator directly modifies class files on a local disk so that + * the classes represented by those class files are reflective. + * After the modification, the class files can be run with the standard JVM + * without <code>javassist.tools.reflect.Loader</code> + * or any other user-defined class loader. + * + * <p>The modified class files are given as the command-line parameters, + * which are a sequence of fully-qualified class names followed by options: + * + * <p><code>-m <i>classname</i></code> : specifies the class of the + * metaobjects associated with instances of the class followed by + * this option. The default is <code>javassit.reflect.Metaobject</code>. + * + * <p><code>-c <i>classname</i></code> : specifies the class of the + * class metaobjects associated with instances of the class followed by + * this option. The default is <code>javassit.reflect.ClassMetaobject</code>. + * + * <p>If a class name is not followed by any options, the class indicated + * by that class name is not reflective. + * + * <p>For example, + * <ul><pre>% java Compiler Dog -m MetaDog -c CMetaDog Cat -m MetaCat Cow + * </pre></ul> + * + * <p>modifies class files <code>Dog.class</code>, <code>Cat.class</code>, + * and <code>Cow.class</code>. + * The metaobject of a Dog object is a MetaDog object and the class + * metaobject is a CMetaDog object. + * The metaobject of a Cat object is a MetaCat object but + * the class metaobject is a default one. + * Cow objects are not reflective. + * + * <p>Note that if the super class is also made reflective, it must be done + * before the sub class. + * + * @see javassist.tools.reflect.Metaobject + * @see javassist.tools.reflect.ClassMetaobject + * @see javassist.tools.reflect.Reflection + */ +public class Compiler { + + public static void main(String[] args) throws Exception { + if (args.length == 0) { + help(System.err); + return; + } + + CompiledClass[] entries = new CompiledClass[args.length]; + int n = parse(args, entries); + + if (n < 1) { + System.err.println("bad parameter."); + return; + } + + processClasses(entries, n); + } + + private static void processClasses(CompiledClass[] entries, int n) + throws Exception + { + Reflection implementor = new Reflection(); + ClassPool pool = ClassPool.getDefault(); + implementor.start(pool); + + for (int i = 0; i < n; ++i) { + CtClass c = pool.get(entries[i].classname); + if (entries[i].metaobject != null + || entries[i].classobject != null) { + String metaobj, classobj; + + if (entries[i].metaobject == null) + metaobj = "javassist.tools.reflect.Metaobject"; + else + metaobj = entries[i].metaobject; + + if (entries[i].classobject == null) + classobj = "javassist.tools.reflect.ClassMetaobject"; + else + classobj = entries[i].classobject; + + if (!implementor.makeReflective(c, pool.get(metaobj), + pool.get(classobj))) + System.err.println("Warning: " + c.getName() + + " is reflective. It was not changed."); + + System.err.println(c.getName() + ": " + metaobj + ", " + + classobj); + } + else + System.err.println(c.getName() + ": not reflective"); + } + + for (int i = 0; i < n; ++i) { + implementor.onLoad(pool, entries[i].classname); + pool.get(entries[i].classname).writeFile(); + } + } + + private static int parse(String[] args, CompiledClass[] result) { + int n = -1; + for (int i = 0; i < args.length; ++i) { + String a = args[i]; + if (a.equals("-m")) + if (n < 0 || i + 1 > args.length) + return -1; + else + result[n].metaobject = args[++i]; + else if (a.equals("-c")) + if (n < 0 || i + 1 > args.length) + return -1; + else + result[n].classobject = args[++i]; + else if (a.charAt(0) == '-') + return -1; + else { + CompiledClass cc = new CompiledClass(); + cc.classname = a; + cc.metaobject = null; + cc.classobject = null; + result[++n] = cc; + } + } + + return n + 1; + } + + private static void help(PrintStream out) { + out.println("Usage: java javassist.tools.reflect.Compiler"); + out.println(" (<class> [-m <metaobject>] [-c <class metaobject>])+"); + } +} diff --git a/src/main/javassist/tools/reflect/Loader.java b/src/main/javassist/tools/reflect/Loader.java new file mode 100644 index 0000000..a9aef4b --- /dev/null +++ b/src/main/javassist/tools/reflect/Loader.java @@ -0,0 +1,163 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.tools.reflect; + +import javassist.CannotCompileException; +import javassist.NotFoundException; +import javassist.ClassPool; + +/** + * A class loader for reflection. + * + * <p>To run a program, say <code>MyApp</code>, + * including a reflective class, + * you must write a start-up program as follows: + * + * <ul><pre> + * public class Main { + * public static void main(String[] args) throws Throwable { + * javassist.tools.reflect.Loader cl + * = (javassist.tools.reflect.Loader)Main.class.getClassLoader(); + * cl.makeReflective("Person", "MyMetaobject", + * "javassist.tools.reflect.ClassMetaobject"); + * cl.run("MyApp", args); + * } + * } + * </pre></ul> + * + * <p>Then run this program as follows: + * + * <ul><pre>% java javassist.tools.reflect.Loader Main arg1, ...</pre></ul> + * + * <p>This command runs <code>Main.main()</code> with <code>arg1</code>, ... + * and <code>Main.main()</code> runs <code>MyApp.main()</code> with + * <code>arg1</code>, ... + * The <code>Person</code> class is modified + * to be a reflective class. Method calls on a <code>Person</code> + * object are intercepted by an instance of <code>MyMetaobject</code>. + * + * <p>Also, you can run <code>MyApp</code> in a slightly different way: + * + * <ul><pre> + * public class Main2 { + * public static void main(String[] args) throws Throwable { + * javassist.tools.reflect.Loader cl = new javassist.tools.reflect.Loader(); + * cl.makeReflective("Person", "MyMetaobject", + * "javassist.tools.reflect.ClassMetaobject"); + * cl.run("MyApp", args); + * } + * } + * </pre></ul> + * + * <p>This program is run as follows: + * + * <ul><pre>% java Main2 arg1, ...</pre></ul> + * + * <p>The difference from the former one is that the class <code>Main</code> + * is loaded by <code>javassist.tools.reflect.Loader</code> whereas the class + * <code>Main2</code> is not. Thus, <code>Main</code> belongs + * to the same name space (security domain) as <code>MyApp</code> + * whereas <code>Main2</code> does not; <code>Main2</code> belongs + * to the same name space as <code>javassist.tools.reflect.Loader</code>. + * For more details, + * see the notes in the manual page of <code>javassist.Loader</code>. + * + * <p>The class <code>Main2</code> is equivalent to this class: + * + * <ul><pre> + * public class Main3 { + * public static void main(String[] args) throws Throwable { + * Reflection reflection = new Reflection(); + * javassist.Loader cl + * = new javassist.Loader(ClassPool.getDefault(reflection)); + * reflection.makeReflective("Person", "MyMetaobject", + * "javassist.tools.reflect.ClassMetaobject"); + * cl.run("MyApp", args); + * } + * } + * </pre></ul> + * + * <p><b>Note:</b> + * + * <p><code>javassist.tools.reflect.Loader</code> does not make a class reflective + * if that class is in a <code>java.*</code> or + * <code>javax.*</code> pacakge because of the specifications + * on the class loading algorithm of Java. The JVM does not allow to + * load such a system class with a user class loader. + * + * <p>To avoid this limitation, those classes should be statically + * modified with <code>javassist.tools.reflect.Compiler</code> and the original + * class files should be replaced. + * + * @see javassist.tools.reflect.Reflection + * @see javassist.tools.reflect.Compiler + * @see javassist.Loader + */ +public class Loader extends javassist.Loader { + protected Reflection reflection; + + /** + * Loads a class with an instance of <code>Loader</code> + * and calls <code>main()</code> in that class. + * + * @param args command line parameters. + * <ul> + * <code>args[0]</code> is the class name to be loaded. + * <br><code>args[1..n]</code> are parameters passed + * to the target <code>main()</code>. + * </ul> + */ + public static void main(String[] args) throws Throwable { + Loader cl = new Loader(); + cl.run(args); + } + + /** + * Constructs a new class loader. + */ + public Loader() throws CannotCompileException, NotFoundException { + super(); + delegateLoadingOf("javassist.tools.reflect.Loader"); + + reflection = new Reflection(); + ClassPool pool = ClassPool.getDefault(); + addTranslator(pool, reflection); + } + + /** + * Produces a reflective class. + * If the super class is also made reflective, it must be done + * before the sub class. + * + * @param clazz the reflective class. + * @param metaobject the class of metaobjects. + * It must be a subclass of + * <code>Metaobject</code>. + * @param metaclass the class of the class metaobject. + * It must be a subclass of + * <code>ClassMetaobject</code>. + * @return <code>false</code> if the class is already reflective. + * + * @see javassist.tools.reflect.Metaobject + * @see javassist.tools.reflect.ClassMetaobject + */ + public boolean makeReflective(String clazz, + String metaobject, String metaclass) + throws CannotCompileException, NotFoundException + { + return reflection.makeReflective(clazz, metaobject, metaclass); + } +} diff --git a/src/main/javassist/tools/reflect/Metalevel.java b/src/main/javassist/tools/reflect/Metalevel.java new file mode 100644 index 0000000..4361fac --- /dev/null +++ b/src/main/javassist/tools/reflect/Metalevel.java @@ -0,0 +1,38 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.tools.reflect; + +/** + * An interface to access a metaobject and a class metaobject. + * This interface is implicitly implemented by the reflective + * class. + */ +public interface Metalevel { + /** + * Obtains the class metaobject associated with this object. + */ + ClassMetaobject _getClass(); + + /** + * Obtains the metaobject associated with this object. + */ + Metaobject _getMetaobject(); + + /** + * Changes the metaobject associated with this object. + */ + void _setMetaobject(Metaobject m); +} diff --git a/src/main/javassist/tools/reflect/Metaobject.java b/src/main/javassist/tools/reflect/Metaobject.java new file mode 100644 index 0000000..cd3a5f5 --- /dev/null +++ b/src/main/javassist/tools/reflect/Metaobject.java @@ -0,0 +1,236 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.tools.reflect; + +import java.lang.reflect.Method; +import java.io.Serializable; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + +/** + * A runtime metaobject. + * + * <p>A <code>Metaobject</code> is created for + * every object at the base level. A different reflective object is + * associated with a different metaobject. + * + * <p>The metaobject intercepts method calls + * on the reflective object at the base-level. To change the behavior + * of the method calls, a subclass of <code>Metaobject</code> + * should be defined. + * + * <p>To obtain a metaobject, calls <code>_getMetaobject()</code> + * on a reflective object. For example, + * + * <ul><pre>Metaobject m = ((Metalevel)reflectiveObject)._getMetaobject(); + * </pre></ul> + * + * @see javassist.tools.reflect.ClassMetaobject + * @see javassist.tools.reflect.Metalevel + */ +public class Metaobject implements Serializable { + protected ClassMetaobject classmetaobject; + protected Metalevel baseobject; + protected Method[] methods; + + /** + * Constructs a <code>Metaobject</code>. The metaobject is + * constructed before the constructor is called on the base-level + * object. + * + * @param self the object that this metaobject is associated with. + * @param args the parameters passed to the constructor of + * <code>self</code>. + */ + public Metaobject(Object self, Object[] args) { + baseobject = (Metalevel)self; + classmetaobject = baseobject._getClass(); + methods = classmetaobject.getReflectiveMethods(); + } + + /** + * Constructs a <code>Metaobject</code> without initialization. + * If calling this constructor, a subclass should be responsible + * for initialization. + */ + protected Metaobject() { + baseobject = null; + classmetaobject = null; + methods = null; + } + + private void writeObject(ObjectOutputStream out) throws IOException { + out.writeObject(baseobject); + } + + private void readObject(ObjectInputStream in) + throws IOException, ClassNotFoundException + { + baseobject = (Metalevel)in.readObject(); + classmetaobject = baseobject._getClass(); + methods = classmetaobject.getReflectiveMethods(); + } + + /** + * Obtains the class metaobject associated with this metaobject. + * + * @see javassist.tools.reflect.ClassMetaobject + */ + public final ClassMetaobject getClassMetaobject() { + return classmetaobject; + } + + /** + * Obtains the object controlled by this metaobject. + */ + public final Object getObject() { + return baseobject; + } + + /** + * Changes the object controlled by this metaobject. + * + * @param self the object + */ + public final void setObject(Object self) { + baseobject = (Metalevel)self; + classmetaobject = baseobject._getClass(); + methods = classmetaobject.getReflectiveMethods(); + + // call _setMetaobject() after the metaobject is settled. + baseobject._setMetaobject(this); + } + + /** + * Returns the name of the method specified + * by <code>identifier</code>. + */ + public final String getMethodName(int identifier) { + String mname = methods[identifier].getName(); + int j = ClassMetaobject.methodPrefixLen; + for (;;) { + char c = mname.charAt(j++); + if (c < '0' || '9' < c) + break; + } + + return mname.substring(j); + } + + /** + * Returns an array of <code>Class</code> objects representing the + * formal parameter types of the method specified + * by <code>identifier</code>. + */ + public final Class[] getParameterTypes(int identifier) { + return methods[identifier].getParameterTypes(); + } + + /** + * Returns a <code>Class</code> objects representing the + * return type of the method specified by <code>identifier</code>. + */ + public final Class getReturnType(int identifier) { + return methods[identifier].getReturnType(); + } + + /** + * Is invoked when public fields of the base-level + * class are read and the runtime system intercepts it. + * This method simply returns the value of the field. + * + * <p>Every subclass of this class should redefine this method. + */ + public Object trapFieldRead(String name) { + Class jc = getClassMetaobject().getJavaClass(); + try { + return jc.getField(name).get(getObject()); + } + catch (NoSuchFieldException e) { + throw new RuntimeException(e.toString()); + } + catch (IllegalAccessException e) { + throw new RuntimeException(e.toString()); + } + } + + /** + * Is invoked when public fields of the base-level + * class are modified and the runtime system intercepts it. + * This method simply sets the field to the given value. + * + * <p>Every subclass of this class should redefine this method. + */ + public void trapFieldWrite(String name, Object value) { + Class jc = getClassMetaobject().getJavaClass(); + try { + jc.getField(name).set(getObject(), value); + } + catch (NoSuchFieldException e) { + throw new RuntimeException(e.toString()); + } + catch (IllegalAccessException e) { + throw new RuntimeException(e.toString()); + } + } + + /** + * Is invoked when base-level method invocation is intercepted. + * This method simply executes the intercepted method invocation + * with the original parameters and returns the resulting value. + * + * <p>Every subclass of this class should redefine this method. + * + * <p>Note: this method is not invoked if the base-level method + * is invoked by a constructor in the super class. For example, + * + * <ul><pre>abstract class A { + * abstract void initialize(); + * A() { + * initialize(); // not intercepted + * } + * } + * + * class B extends A { + * void initialize() { System.out.println("initialize()"); } + * B() { + * super(); + * initialize(); // intercepted + * } + * }</pre></ul> + * + * <p>if an instance of B is created, + * the invocation of initialize() in B is intercepted only once. + * The first invocation by the constructor in A is not intercepted. + * This is because the link between a base-level object and a + * metaobject is not created until the execution of a + * constructor of the super class finishes. + */ + public Object trapMethodcall(int identifier, Object[] args) + throws Throwable + { + try { + return methods[identifier].invoke(getObject(), args); + } + catch (java.lang.reflect.InvocationTargetException e) { + throw e.getTargetException(); + } + catch (java.lang.IllegalAccessException e) { + throw new CannotInvokeException(e); + } + } +} diff --git a/src/main/javassist/tools/reflect/Reflection.java b/src/main/javassist/tools/reflect/Reflection.java new file mode 100644 index 0000000..cb93096 --- /dev/null +++ b/src/main/javassist/tools/reflect/Reflection.java @@ -0,0 +1,384 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.tools.reflect; + +import javassist.*; +import javassist.CtMethod.ConstParameter; + +/** + * The class implementing the behavioral reflection mechanism. + * + * <p>If a class is reflective, + * then all the method invocations on every + * instance of that class are intercepted by the runtime + * metaobject controlling that instance. The methods inherited from the + * super classes are also intercepted except final methods. To intercept + * a final method in a super class, that super class must be also reflective. + * + * <p>To do this, the original class file representing a reflective class: + * + * <ul><pre> + * class Person { + * public int f(int i) { return i + 1; } + * public int value; + * } + * </pre></ul> + * + * <p>is modified so that it represents a class: + * + * <ul><pre> + * class Person implements Metalevel { + * public int _original_f(int i) { return i + 1; } + * public int f(int i) { <i>delegate to the metaobject</i> } + * + * public int value; + * public int _r_value() { <i>read "value"</i> } + * public void _w_value(int v) { <i>write "value"</i> } + * + * public ClassMetaobject _getClass() { <i>return a class metaobject</i> } + * public Metaobject _getMetaobject() { <i>return a metaobject</i> } + * public void _setMetaobject(Metaobject m) { <i>change a metaobject</i> } + * } + * </pre></ul> + * + * @see javassist.tools.reflect.ClassMetaobject + * @see javassist.tools.reflect.Metaobject + * @see javassist.tools.reflect.Loader + * @see javassist.tools.reflect.Compiler + */ +public class Reflection implements Translator { + + static final String classobjectField = "_classobject"; + static final String classobjectAccessor = "_getClass"; + static final String metaobjectField = "_metaobject"; + static final String metaobjectGetter = "_getMetaobject"; + static final String metaobjectSetter = "_setMetaobject"; + static final String readPrefix = "_r_"; + static final String writePrefix = "_w_"; + + static final String metaobjectClassName = "javassist.tools.reflect.Metaobject"; + static final String classMetaobjectClassName + = "javassist.tools.reflect.ClassMetaobject"; + + protected CtMethod trapMethod, trapStaticMethod; + protected CtMethod trapRead, trapWrite; + protected CtClass[] readParam; + + protected ClassPool classPool; + protected CodeConverter converter; + + private boolean isExcluded(String name) { + return name.startsWith(ClassMetaobject.methodPrefix) + || name.equals(classobjectAccessor) + || name.equals(metaobjectSetter) + || name.equals(metaobjectGetter) + || name.startsWith(readPrefix) + || name.startsWith(writePrefix); + } + + /** + * Constructs a new <code>Reflection</code> object. + */ + public Reflection() { + classPool = null; + converter = new CodeConverter(); + } + + /** + * Initializes the object. + */ + public void start(ClassPool pool) throws NotFoundException { + classPool = pool; + final String msg + = "javassist.tools.reflect.Sample is not found or broken."; + try { + CtClass c = classPool.get("javassist.tools.reflect.Sample"); + trapMethod = c.getDeclaredMethod("trap"); + trapStaticMethod = c.getDeclaredMethod("trapStatic"); + trapRead = c.getDeclaredMethod("trapRead"); + trapWrite = c.getDeclaredMethod("trapWrite"); + readParam + = new CtClass[] { classPool.get("java.lang.Object") }; + } + catch (NotFoundException e) { + throw new RuntimeException(msg); + } + } + + /** + * Inserts hooks for intercepting accesses to the fields declared + * in reflective classes. + */ + public void onLoad(ClassPool pool, String classname) + throws CannotCompileException, NotFoundException + { + CtClass clazz = pool.get(classname); + clazz.instrument(converter); + } + + /** + * Produces a reflective class. + * If the super class is also made reflective, it must be done + * before the sub class. + * + * @param classname the name of the reflective class + * @param metaobject the class name of metaobjects. + * @param metaclass the class name of the class metaobject. + * @return <code>false</code> if the class is already reflective. + * + * @see javassist.tools.reflect.Metaobject + * @see javassist.tools.reflect.ClassMetaobject + */ + public boolean makeReflective(String classname, + String metaobject, String metaclass) + throws CannotCompileException, NotFoundException + { + return makeReflective(classPool.get(classname), + classPool.get(metaobject), + classPool.get(metaclass)); + } + + /** + * Produces a reflective class. + * If the super class is also made reflective, it must be done + * before the sub class. + * + * @param clazz the reflective class. + * @param metaobject the class of metaobjects. + * It must be a subclass of + * <code>Metaobject</code>. + * @param metaclass the class of the class metaobject. + * It must be a subclass of + * <code>ClassMetaobject</code>. + * @return <code>false</code> if the class is already reflective. + * + * @see javassist.tools.reflect.Metaobject + * @see javassist.tools.reflect.ClassMetaobject + */ + public boolean makeReflective(Class clazz, + Class metaobject, Class metaclass) + throws CannotCompileException, NotFoundException + { + return makeReflective(clazz.getName(), metaobject.getName(), + metaclass.getName()); + } + + /** + * Produces a reflective class. It modifies the given + * <code>CtClass</code> object and makes it reflective. + * If the super class is also made reflective, it must be done + * before the sub class. + * + * @param clazz the reflective class. + * @param metaobject the class of metaobjects. + * It must be a subclass of + * <code>Metaobject</code>. + * @param metaclass the class of the class metaobject. + * It must be a subclass of + * <code>ClassMetaobject</code>. + * @return <code>false</code> if the class is already reflective. + * + * @see javassist.tools.reflect.Metaobject + * @see javassist.tools.reflect.ClassMetaobject + */ + public boolean makeReflective(CtClass clazz, + CtClass metaobject, CtClass metaclass) + throws CannotCompileException, CannotReflectException, + NotFoundException + { + if (clazz.isInterface()) + throw new CannotReflectException( + "Cannot reflect an interface: " + clazz.getName()); + + if (clazz.subclassOf(classPool.get(classMetaobjectClassName))) + throw new CannotReflectException( + "Cannot reflect a subclass of ClassMetaobject: " + + clazz.getName()); + + if (clazz.subclassOf(classPool.get(metaobjectClassName))) + throw new CannotReflectException( + "Cannot reflect a subclass of Metaobject: " + + clazz.getName()); + + registerReflectiveClass(clazz); + return modifyClassfile(clazz, metaobject, metaclass); + } + + /** + * Registers a reflective class. The field accesses to the instances + * of this class are instrumented. + */ + private void registerReflectiveClass(CtClass clazz) { + CtField[] fs = clazz.getDeclaredFields(); + for (int i = 0; i < fs.length; ++i) { + CtField f = fs[i]; + int mod = f.getModifiers(); + if ((mod & Modifier.PUBLIC) != 0 && (mod & Modifier.FINAL) == 0) { + String name = f.getName(); + converter.replaceFieldRead(f, clazz, readPrefix + name); + converter.replaceFieldWrite(f, clazz, writePrefix + name); + } + } + } + + private boolean modifyClassfile(CtClass clazz, CtClass metaobject, + CtClass metaclass) + throws CannotCompileException, NotFoundException + { + if (clazz.getAttribute("Reflective") != null) + return false; // this is already reflective. + else + clazz.setAttribute("Reflective", new byte[0]); + + CtClass mlevel = classPool.get("javassist.tools.reflect.Metalevel"); + boolean addMeta = !clazz.subtypeOf(mlevel); + if (addMeta) + clazz.addInterface(mlevel); + + processMethods(clazz, addMeta); + processFields(clazz); + + CtField f; + if (addMeta) { + f = new CtField(classPool.get("javassist.tools.reflect.Metaobject"), + metaobjectField, clazz); + f.setModifiers(Modifier.PROTECTED); + clazz.addField(f, CtField.Initializer.byNewWithParams(metaobject)); + + clazz.addMethod(CtNewMethod.getter(metaobjectGetter, f)); + clazz.addMethod(CtNewMethod.setter(metaobjectSetter, f)); + } + + f = new CtField(classPool.get("javassist.tools.reflect.ClassMetaobject"), + classobjectField, clazz); + f.setModifiers(Modifier.PRIVATE | Modifier.STATIC); + clazz.addField(f, CtField.Initializer.byNew(metaclass, + new String[] { clazz.getName() })); + + clazz.addMethod(CtNewMethod.getter(classobjectAccessor, f)); + return true; + } + + private void processMethods(CtClass clazz, boolean dontSearch) + throws CannotCompileException, NotFoundException + { + CtMethod[] ms = clazz.getMethods(); + for (int i = 0; i < ms.length; ++i) { + CtMethod m = ms[i]; + int mod = m.getModifiers(); + if (Modifier.isPublic(mod) && !Modifier.isAbstract(mod)) + processMethods0(mod, clazz, m, i, dontSearch); + } + } + + private void processMethods0(int mod, CtClass clazz, + CtMethod m, int identifier, boolean dontSearch) + throws CannotCompileException, NotFoundException + { + CtMethod body; + String name = m.getName(); + + if (isExcluded(name)) // internally-used method inherited + return; // from a reflective class. + + CtMethod m2; + if (m.getDeclaringClass() == clazz) { + if (Modifier.isNative(mod)) + return; + + m2 = m; + if (Modifier.isFinal(mod)) { + mod &= ~Modifier.FINAL; + m2.setModifiers(mod); + } + } + else { + if (Modifier.isFinal(mod)) + return; + + mod &= ~Modifier.NATIVE; + m2 = CtNewMethod.delegator(findOriginal(m, dontSearch), clazz); + m2.setModifiers(mod); + clazz.addMethod(m2); + } + + m2.setName(ClassMetaobject.methodPrefix + identifier + + "_" + name); + + if (Modifier.isStatic(mod)) + body = trapStaticMethod; + else + body = trapMethod; + + CtMethod wmethod + = CtNewMethod.wrapped(m.getReturnType(), name, + m.getParameterTypes(), m.getExceptionTypes(), + body, ConstParameter.integer(identifier), + clazz); + wmethod.setModifiers(mod); + clazz.addMethod(wmethod); + } + + private CtMethod findOriginal(CtMethod m, boolean dontSearch) + throws NotFoundException + { + if (dontSearch) + return m; + + String name = m.getName(); + CtMethod[] ms = m.getDeclaringClass().getDeclaredMethods(); + for (int i = 0; i < ms.length; ++i) { + String orgName = ms[i].getName(); + if (orgName.endsWith(name) + && orgName.startsWith(ClassMetaobject.methodPrefix) + && ms[i].getSignature().equals(m.getSignature())) + return ms[i]; + } + + return m; + } + + private void processFields(CtClass clazz) + throws CannotCompileException, NotFoundException + { + CtField[] fs = clazz.getDeclaredFields(); + for (int i = 0; i < fs.length; ++i) { + CtField f = fs[i]; + int mod = f.getModifiers(); + if ((mod & Modifier.PUBLIC) != 0 && (mod & Modifier.FINAL) == 0) { + mod |= Modifier.STATIC; + String name = f.getName(); + CtClass ftype = f.getType(); + CtMethod wmethod + = CtNewMethod.wrapped(ftype, readPrefix + name, + readParam, null, trapRead, + ConstParameter.string(name), + clazz); + wmethod.setModifiers(mod); + clazz.addMethod(wmethod); + CtClass[] writeParam = new CtClass[2]; + writeParam[0] = classPool.get("java.lang.Object"); + writeParam[1] = ftype; + wmethod = CtNewMethod.wrapped(CtClass.voidType, + writePrefix + name, + writeParam, null, trapWrite, + ConstParameter.string(name), clazz); + wmethod.setModifiers(mod); + clazz.addMethod(wmethod); + } + } + } +} diff --git a/src/main/javassist/tools/reflect/Sample.java b/src/main/javassist/tools/reflect/Sample.java new file mode 100644 index 0000000..d76df19 --- /dev/null +++ b/src/main/javassist/tools/reflect/Sample.java @@ -0,0 +1,56 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.tools.reflect; + +/** + * A template used for defining a reflective class. + */ +public class Sample { + private Metaobject _metaobject; + private static ClassMetaobject _classobject; + + public Object trap(Object[] args, int identifier) throws Throwable { + Metaobject mobj; + mobj = _metaobject; + if (mobj == null) + return ClassMetaobject.invoke(this, identifier, args); + else + return mobj.trapMethodcall(identifier, args); + } + + public static Object trapStatic(Object[] args, int identifier) + throws Throwable + { + return _classobject.trapMethodcall(identifier, args); + } + + public static Object trapRead(Object[] args, String name) { + if (args[0] == null) + return _classobject.trapFieldRead(name); + else + return ((Metalevel)args[0])._getMetaobject().trapFieldRead(name); + } + + public static Object trapWrite(Object[] args, String name) { + Metalevel base = (Metalevel)args[0]; + if (base == null) + _classobject.trapFieldWrite(name, args[1]); + else + base._getMetaobject().trapFieldWrite(name, args[1]); + + return null; + } +} diff --git a/src/main/javassist/tools/reflect/package.html b/src/main/javassist/tools/reflect/package.html new file mode 100644 index 0000000..10a4196 --- /dev/null +++ b/src/main/javassist/tools/reflect/package.html @@ -0,0 +1,35 @@ +<html> +<body> +Runtime Behavioral Reflection. + +<p>(also recently known as interceptors or AOP?) + +<p>This package enables a metaobject to trap method calls and field +accesses on a regular Java object. It provides a class +<code>Reflection</code>, which is a main module for implementing +runtime behavioral reflection. +It also provides +a class <code>Loader</code> and <code>Compiler</code> +as utilities for dynamically or statically +translating a regular class into a reflective class. + +<p>An instance of the reflective class is associated with +a runtime metaobject and a runtime class metaobject, which control +the behavior of that instance. +The runtime +metaobject is created for every (base-level) instance but the +runtime class metaobject is created for every (base-level) class. +<code>Metaobject</code> is the root class of the runtime +metaobject and <code>ClassMetaobject</code> is the root class +of the runtime class metaobject. + +<p>This package is provided as a sample implementation of the +reflection mechanism with Javassist. All the programs in this package +uses only the regular Javassist API; they never call any hidden +methods. + +<p>The most significant class in this package is <code>Reflection</code>. +See the description of this class first. + +</body> +</html> diff --git a/src/main/javassist/tools/rmi/AppletServer.java b/src/main/javassist/tools/rmi/AppletServer.java new file mode 100644 index 0000000..b2678f3 --- /dev/null +++ b/src/main/javassist/tools/rmi/AppletServer.java @@ -0,0 +1,250 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.tools.rmi; + +import java.io.*; + +import javassist.tools.web.*; +import javassist.CannotCompileException; +import javassist.NotFoundException; +import javassist.ClassPool; +import java.lang.reflect.Method; +import java.util.Hashtable; +import java.util.Vector; + +/** + * An AppletServer object is a web server that an ObjectImporter + * communicates with. It makes the objects specified by + * <code>exportObject()</code> remotely accessible from applets. + * If the classes of the exported objects are requested by the client-side + * JVM, this web server sends proxy classes for the requested classes. + * + * @see javassist.tools.rmi.ObjectImporter + */ +public class AppletServer extends Webserver { + private StubGenerator stubGen; + private Hashtable exportedNames; + private Vector exportedObjects; + + private static final byte[] okHeader + = "HTTP/1.0 200 OK\r\n\r\n".getBytes(); + + /** + * Constructs a web server. + * + * @param port port number + */ + public AppletServer(String port) + throws IOException, NotFoundException, CannotCompileException + { + this(Integer.parseInt(port)); + } + + /** + * Constructs a web server. + * + * @param port port number + */ + public AppletServer(int port) + throws IOException, NotFoundException, CannotCompileException + { + this(ClassPool.getDefault(), new StubGenerator(), port); + } + + /** + * Constructs a web server. + * + * @param port port number + * @param src the source of classs files. + */ + public AppletServer(int port, ClassPool src) + throws IOException, NotFoundException, CannotCompileException + { + this(new ClassPool(src), new StubGenerator(), port); + } + + private AppletServer(ClassPool loader, StubGenerator gen, int port) + throws IOException, NotFoundException, CannotCompileException + { + super(port); + exportedNames = new Hashtable(); + exportedObjects = new Vector(); + stubGen = gen; + addTranslator(loader, gen); + } + + /** + * Begins the HTTP service. + */ + public void run() { + super.run(); + } + + /** + * Exports an object. + * This method produces the bytecode of the proxy class used + * to access the exported object. A remote applet can load + * the proxy class and call a method on the exported object. + * + * @param name the name used for looking the object up. + * @param obj the exported object. + * @return the object identifier + * + * @see javassist.tools.rmi.ObjectImporter#lookupObject(String) + */ + public synchronized int exportObject(String name, Object obj) + throws CannotCompileException + { + Class clazz = obj.getClass(); + ExportedObject eo = new ExportedObject(); + eo.object = obj; + eo.methods = clazz.getMethods(); + exportedObjects.addElement(eo); + eo.identifier = exportedObjects.size() - 1; + if (name != null) + exportedNames.put(name, eo); + + try { + stubGen.makeProxyClass(clazz); + } + catch (NotFoundException e) { + throw new CannotCompileException(e); + } + + return eo.identifier; + } + + /** + * Processes a request from a web browser (an ObjectImporter). + */ + public void doReply(InputStream in, OutputStream out, String cmd) + throws IOException, BadHttpRequest + { + if (cmd.startsWith("POST /rmi ")) + processRMI(in, out); + else if (cmd.startsWith("POST /lookup ")) + lookupName(cmd, in, out); + else + super.doReply(in, out, cmd); + } + + private void processRMI(InputStream ins, OutputStream outs) + throws IOException + { + ObjectInputStream in = new ObjectInputStream(ins); + + int objectId = in.readInt(); + int methodId = in.readInt(); + Exception err = null; + Object rvalue = null; + try { + ExportedObject eo + = (ExportedObject)exportedObjects.elementAt(objectId); + Object[] args = readParameters(in); + rvalue = convertRvalue(eo.methods[methodId].invoke(eo.object, + args)); + } + catch(Exception e) { + err = e; + logging2(e.toString()); + } + + outs.write(okHeader); + ObjectOutputStream out = new ObjectOutputStream(outs); + if (err != null) { + out.writeBoolean(false); + out.writeUTF(err.toString()); + } + else + try { + out.writeBoolean(true); + out.writeObject(rvalue); + } + catch (NotSerializableException e) { + logging2(e.toString()); + } + catch (InvalidClassException e) { + logging2(e.toString()); + } + + out.flush(); + out.close(); + in.close(); + } + + private Object[] readParameters(ObjectInputStream in) + throws IOException, ClassNotFoundException + { + int n = in.readInt(); + Object[] args = new Object[n]; + for (int i = 0; i < n; ++i) { + Object a = in.readObject(); + if (a instanceof RemoteRef) { + RemoteRef ref = (RemoteRef)a; + ExportedObject eo + = (ExportedObject)exportedObjects.elementAt(ref.oid); + a = eo.object; + } + + args[i] = a; + } + + return args; + } + + private Object convertRvalue(Object rvalue) + throws CannotCompileException + { + if (rvalue == null) + return null; // the return type is void. + + String classname = rvalue.getClass().getName(); + if (stubGen.isProxyClass(classname)) + return new RemoteRef(exportObject(null, rvalue), classname); + else + return rvalue; + } + + private void lookupName(String cmd, InputStream ins, OutputStream outs) + throws IOException + { + ObjectInputStream in = new ObjectInputStream(ins); + String name = DataInputStream.readUTF(in); + ExportedObject found = (ExportedObject)exportedNames.get(name); + outs.write(okHeader); + ObjectOutputStream out = new ObjectOutputStream(outs); + if (found == null) { + logging2(name + "not found."); + out.writeInt(-1); // error code + out.writeUTF("error"); + } + else { + logging2(name); + out.writeInt(found.identifier); + out.writeUTF(found.object.getClass().getName()); + } + + out.flush(); + out.close(); + in.close(); + } +} + +class ExportedObject { + public int identifier; + public Object object; + public Method[] methods; +} diff --git a/src/main/javassist/tools/rmi/ObjectImporter.java b/src/main/javassist/tools/rmi/ObjectImporter.java new file mode 100644 index 0000000..e6692ef --- /dev/null +++ b/src/main/javassist/tools/rmi/ObjectImporter.java @@ -0,0 +1,298 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.tools.rmi; + +import java.io.*; +import java.net.*; +import java.applet.Applet; +import java.lang.reflect.*; + +/** + * The object importer enables applets to call a method on a remote + * object running on the <code>Webserver</code> (the <b>main</b> class of this + * package). + * + * <p>To access the remote + * object, the applet first calls <code>lookupObject()</code> and + * obtains a proxy object, which is a reference to that object. + * The class name of the proxy object is identical to that of + * the remote object. + * The proxy object provides the same set of methods as the remote object. + * If one of the methods is invoked on the proxy object, + * the invocation is delegated to the remote object. + * From the viewpoint of the applet, therefore, the two objects are + * identical. The applet can access the object on the server + * with the regular Java syntax without concern about the actual + * location. + * + * <p>The methods remotely called by the applet must be <code>public</code>. + * This is true even if the applet's class and the remote object's classs + * belong to the same package. + * + * <p>If class X is a class of remote objects, a subclass of X must be + * also a class of remote objects. On the other hand, this restriction + * is not applied to the superclass of X. The class X does not have to + * contain a constructor taking no arguments. + * + * <p>The parameters to a remote method is passed in the <i>call-by-value</i> + * manner. Thus all the parameter classes must implement + * <code>java.io.Serializable</code>. However, if the parameter is the + * proxy object, the reference to the remote object instead of a copy of + * the object is passed to the method. + * + * <p>Because of the limitations of the current implementation, + * <ul> + * <li>The parameter objects cannot contain the proxy + * object as a field value. + * <li>If class <code>C</code> is of the remote object, then + * the applet cannot instantiate <code>C</code> locally or remotely. + * </ul> + * + * <p>All the exceptions thrown by the remote object are converted + * into <code>RemoteException</code>. Since this exception is a subclass + * of <code>RuntimeException</code>, the caller method does not need + * to catch the exception. However, good programs should catch + * the <code>RuntimeException</code>. + * + * @see javassist.tools.rmi.AppletServer + * @see javassist.tools.rmi.RemoteException + * @see javassist.tools.web.Viewer + */ +public class ObjectImporter implements java.io.Serializable { + private final byte[] endofline = { 0x0d, 0x0a }; + private String servername, orgServername; + private int port, orgPort; + + protected byte[] lookupCommand = "POST /lookup HTTP/1.0".getBytes(); + protected byte[] rmiCommand = "POST /rmi HTTP/1.0".getBytes(); + + /** + * Constructs an object importer. + * + * <p>Remote objects are imported from the web server that the given + * applet has been loaded from. + * + * @param applet the applet loaded from the <code>Webserver</code>. + */ + public ObjectImporter(Applet applet) { + URL codebase = applet.getCodeBase(); + orgServername = servername = codebase.getHost(); + orgPort = port = codebase.getPort(); + } + + /** + * Constructs an object importer. + * + * <p>If you run a program with <code>javassist.tools.web.Viewer</code>, + * you can construct an object importer as follows: + * + * <ul><pre> + * Viewer v = (Viewer)this.getClass().getClassLoader(); + * ObjectImporter oi = new ObjectImporter(v.getServer(), v.getPort()); + * </pre></ul> + * + * @see javassist.tools.web.Viewer + */ + public ObjectImporter(String servername, int port) { + this.orgServername = this.servername = servername; + this.orgPort = this.port = port; + } + + /** + * Finds the object exported by a server with the specified name. + * If the object is not found, this method returns null. + * + * @param name the name of the exported object. + * @return the proxy object or null. + */ + public Object getObject(String name) { + try { + return lookupObject(name); + } + catch (ObjectNotFoundException e) { + return null; + } + } + + /** + * Sets an http proxy server. After this method is called, the object + * importer connects a server through the http proxy server. + */ + public void setHttpProxy(String host, int port) { + String proxyHeader = "POST http://" + orgServername + ":" + orgPort; + String cmd = proxyHeader + "/lookup HTTP/1.0"; + lookupCommand = cmd.getBytes(); + cmd = proxyHeader + "/rmi HTTP/1.0"; + rmiCommand = cmd.getBytes(); + this.servername = host; + this.port = port; + } + + /** + * Finds the object exported by the server with the specified name. + * It sends a POST request to the server (via an http proxy server + * if needed). + * + * @param name the name of the exported object. + * @return the proxy object. + */ + public Object lookupObject(String name) throws ObjectNotFoundException + { + try { + Socket sock = new Socket(servername, port); + OutputStream out = sock.getOutputStream(); + out.write(lookupCommand); + out.write(endofline); + out.write(endofline); + + ObjectOutputStream dout = new ObjectOutputStream(out); + dout.writeUTF(name); + dout.flush(); + + InputStream in = new BufferedInputStream(sock.getInputStream()); + skipHeader(in); + ObjectInputStream din = new ObjectInputStream(in); + int n = din.readInt(); + String classname = din.readUTF(); + din.close(); + dout.close(); + sock.close(); + + if (n >= 0) + return createProxy(n, classname); + } + catch (Exception e) { + e.printStackTrace(); + throw new ObjectNotFoundException(name, e); + } + + throw new ObjectNotFoundException(name); + } + + private static final Class[] proxyConstructorParamTypes + = new Class[] { ObjectImporter.class, int.class }; + + private Object createProxy(int oid, String classname) throws Exception { + Class c = Class.forName(classname); + Constructor cons = c.getConstructor(proxyConstructorParamTypes); + return cons.newInstance(new Object[] { this, new Integer(oid) }); + } + + /** + * Calls a method on a remote object. + * It sends a POST request to the server (via an http proxy server + * if needed). + * + * <p>This method is called by only proxy objects. + */ + public Object call(int objectid, int methodid, Object[] args) + throws RemoteException + { + boolean result; + Object rvalue; + String errmsg; + + try { + /* This method establishes a raw tcp connection for sending + * a POST message. Thus the object cannot communicate a + * remote object beyond a fire wall. To avoid this problem, + * the connection should be established with a mechanism + * collaborating a proxy server. Unfortunately, java.lang.URL + * does not seem to provide such a mechanism. + * + * You might think that using HttpURLConnection is a better + * way than constructing a raw tcp connection. Unfortunately, + * URL.openConnection() does not return an HttpURLConnection + * object in Netscape's JVM. It returns a + * netscape.net.URLConnection object. + * + * lookupObject() has the same problem. + */ + Socket sock = new Socket(servername, port); + OutputStream out = new BufferedOutputStream( + sock.getOutputStream()); + out.write(rmiCommand); + out.write(endofline); + out.write(endofline); + + ObjectOutputStream dout = new ObjectOutputStream(out); + dout.writeInt(objectid); + dout.writeInt(methodid); + writeParameters(dout, args); + dout.flush(); + + InputStream ins = new BufferedInputStream(sock.getInputStream()); + skipHeader(ins); + ObjectInputStream din = new ObjectInputStream(ins); + result = din.readBoolean(); + rvalue = null; + errmsg = null; + if (result) + rvalue = din.readObject(); + else + errmsg = din.readUTF(); + + din.close(); + dout.close(); + sock.close(); + + if (rvalue instanceof RemoteRef) { + RemoteRef ref = (RemoteRef)rvalue; + rvalue = createProxy(ref.oid, ref.classname); + } + } + catch (ClassNotFoundException e) { + throw new RemoteException(e); + } + catch (IOException e) { + throw new RemoteException(e); + } + catch (Exception e) { + throw new RemoteException(e); + } + + if (result) + return rvalue; + else + throw new RemoteException(errmsg); + } + + private void skipHeader(InputStream in) throws IOException { + int len; + do { + int c; + len = 0; + while ((c = in.read()) >= 0 && c != 0x0d) + ++len; + + in.read(); /* skip 0x0a (LF) */ + } while (len > 0); + } + + private void writeParameters(ObjectOutputStream dout, Object[] params) + throws IOException + { + int n = params.length; + dout.writeInt(n); + for (int i = 0; i < n; ++i) + if (params[i] instanceof Proxy) { + Proxy p = (Proxy)params[i]; + dout.writeObject(new RemoteRef(p._getObjectId())); + } + else + dout.writeObject(params[i]); + } +} diff --git a/src/main/javassist/tools/rmi/ObjectNotFoundException.java b/src/main/javassist/tools/rmi/ObjectNotFoundException.java new file mode 100644 index 0000000..8ec3a46 --- /dev/null +++ b/src/main/javassist/tools/rmi/ObjectNotFoundException.java @@ -0,0 +1,26 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.tools.rmi; + +public class ObjectNotFoundException extends Exception { + public ObjectNotFoundException(String name) { + super(name + " is not exported"); + } + + public ObjectNotFoundException(String name, Exception e) { + super(name + " because of " + e.toString()); + } +} diff --git a/src/main/javassist/tools/rmi/Proxy.java b/src/main/javassist/tools/rmi/Proxy.java new file mode 100644 index 0000000..5ea8a70 --- /dev/null +++ b/src/main/javassist/tools/rmi/Proxy.java @@ -0,0 +1,25 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.tools.rmi; + +/** + * An interface implemented by proxy classes. + * + * @see javassist.tools.rmi.StubGenerator + */ +public interface Proxy { + int _getObjectId(); +} diff --git a/src/main/javassist/tools/rmi/RemoteException.java b/src/main/javassist/tools/rmi/RemoteException.java new file mode 100644 index 0000000..19a107f --- /dev/null +++ b/src/main/javassist/tools/rmi/RemoteException.java @@ -0,0 +1,30 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.tools.rmi; + +/** + * <code>RemoteException</code> represents any exception thrown + * during remote method invocation. + */ +public class RemoteException extends RuntimeException { + public RemoteException(String msg) { + super(msg); + } + + public RemoteException(Exception e) { + super("by " + e.toString()); + } +} diff --git a/src/main/javassist/tools/rmi/RemoteRef.java b/src/main/javassist/tools/rmi/RemoteRef.java new file mode 100644 index 0000000..fb1c2e1 --- /dev/null +++ b/src/main/javassist/tools/rmi/RemoteRef.java @@ -0,0 +1,35 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.tools.rmi; + +/** + * Remote reference. This class is internally used for sending a remote + * reference through a network stream. + */ +public class RemoteRef implements java.io.Serializable { + public int oid; + public String classname; + + public RemoteRef(int i) { + oid = i; + classname = null; + } + + public RemoteRef(int i, String name) { + oid = i; + classname = name; + } +} diff --git a/src/main/javassist/tools/rmi/Sample.java b/src/main/javassist/tools/rmi/Sample.java new file mode 100644 index 0000000..12c799b --- /dev/null +++ b/src/main/javassist/tools/rmi/Sample.java @@ -0,0 +1,36 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.tools.rmi; + +/** + * A template used for defining a proxy class. + * The class file of this class is read by the <code>StubGenerator</code> + * class. + */ +public class Sample { + private ObjectImporter importer; + private int objectId; + + public Object forward(Object[] args, int identifier) { + return importer.call(objectId, identifier, args); + } + + public static Object forwardStatic(Object[] args, int identifier) + throws RemoteException + { + throw new RemoteException("cannot call a static method."); + } +} diff --git a/src/main/javassist/tools/rmi/StubGenerator.java b/src/main/javassist/tools/rmi/StubGenerator.java new file mode 100644 index 0000000..8b6604a --- /dev/null +++ b/src/main/javassist/tools/rmi/StubGenerator.java @@ -0,0 +1,255 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.tools.rmi; + +import javassist.*; +import java.lang.reflect.Method; +import java.util.Hashtable; +import javassist.CtMethod.ConstParameter; + +/** + * A stub-code generator. It is used for producing a proxy class. + * + * <p>The proxy class for class A is as follows: + * + * <ul><pre>public class A implements Proxy, Serializable { + * private ObjectImporter importer; + * private int objectId; + * public int _getObjectId() { return objectId; } + * public A(ObjectImporter oi, int id) { + * importer = oi; objectId = id; + * } + * + * ... the same methods that the original class A declares ... + * }</pre></ul> + * + * <p>Instances of the proxy class is created by an + * <code>ObjectImporter</code> object. + */ +public class StubGenerator implements Translator { + private static final String fieldImporter = "importer"; + private static final String fieldObjectId = "objectId"; + private static final String accessorObjectId = "_getObjectId"; + private static final String sampleClass = "javassist.tools.rmi.Sample"; + + private ClassPool classPool; + private Hashtable proxyClasses; + private CtMethod forwardMethod; + private CtMethod forwardStaticMethod; + + private CtClass[] proxyConstructorParamTypes; + private CtClass[] interfacesForProxy; + private CtClass[] exceptionForProxy; + + /** + * Constructs a stub-code generator. + */ + public StubGenerator() { + proxyClasses = new Hashtable(); + } + + /** + * Initializes the object. + * This is a method declared in javassist.Translator. + * + * @see javassist.Translator#start(ClassPool) + */ + public void start(ClassPool pool) throws NotFoundException { + classPool = pool; + CtClass c = pool.get(sampleClass); + forwardMethod = c.getDeclaredMethod("forward"); + forwardStaticMethod = c.getDeclaredMethod("forwardStatic"); + + proxyConstructorParamTypes + = pool.get(new String[] { "javassist.tools.rmi.ObjectImporter", + "int" }); + interfacesForProxy + = pool.get(new String[] { "java.io.Serializable", + "javassist.tools.rmi.Proxy" }); + exceptionForProxy + = new CtClass[] { pool.get("javassist.tools.rmi.RemoteException") }; + } + + /** + * Does nothing. + * This is a method declared in javassist.Translator. + * @see javassist.Translator#onLoad(ClassPool,String) + */ + public void onLoad(ClassPool pool, String classname) {} + + /** + * Returns <code>true</code> if the specified class is a proxy class + * recorded by <code>makeProxyClass()</code>. + * + * @param name a fully-qualified class name + */ + public boolean isProxyClass(String name) { + return proxyClasses.get(name) != null; + } + + /** + * Makes a proxy class. The produced class is substituted + * for the original class. + * + * @param clazz the class referenced + * through the proxy class. + * @return <code>false</code> if the proxy class + * has been already produced. + */ + public synchronized boolean makeProxyClass(Class clazz) + throws CannotCompileException, NotFoundException + { + String classname = clazz.getName(); + if (proxyClasses.get(classname) != null) + return false; + else { + CtClass ctclazz = produceProxyClass(classPool.get(classname), + clazz); + proxyClasses.put(classname, ctclazz); + modifySuperclass(ctclazz); + return true; + } + } + + private CtClass produceProxyClass(CtClass orgclass, Class orgRtClass) + throws CannotCompileException, NotFoundException + { + int modify = orgclass.getModifiers(); + if (Modifier.isAbstract(modify) || Modifier.isNative(modify) + || !Modifier.isPublic(modify)) + throw new CannotCompileException(orgclass.getName() + + " must be public, non-native, and non-abstract."); + + CtClass proxy = classPool.makeClass(orgclass.getName(), + orgclass.getSuperclass()); + + proxy.setInterfaces(interfacesForProxy); + + CtField f + = new CtField(classPool.get("javassist.tools.rmi.ObjectImporter"), + fieldImporter, proxy); + f.setModifiers(Modifier.PRIVATE); + proxy.addField(f, CtField.Initializer.byParameter(0)); + + f = new CtField(CtClass.intType, fieldObjectId, proxy); + f.setModifiers(Modifier.PRIVATE); + proxy.addField(f, CtField.Initializer.byParameter(1)); + + proxy.addMethod(CtNewMethod.getter(accessorObjectId, f)); + + proxy.addConstructor(CtNewConstructor.defaultConstructor(proxy)); + CtConstructor cons + = CtNewConstructor.skeleton(proxyConstructorParamTypes, + null, proxy); + proxy.addConstructor(cons); + + try { + addMethods(proxy, orgRtClass.getMethods()); + return proxy; + } + catch (SecurityException e) { + throw new CannotCompileException(e); + } + } + + private CtClass toCtClass(Class rtclass) throws NotFoundException { + String name; + if (!rtclass.isArray()) + name = rtclass.getName(); + else { + StringBuffer sbuf = new StringBuffer(); + do { + sbuf.append("[]"); + rtclass = rtclass.getComponentType(); + } while(rtclass.isArray()); + sbuf.insert(0, rtclass.getName()); + name = sbuf.toString(); + } + + return classPool.get(name); + } + + private CtClass[] toCtClass(Class[] rtclasses) throws NotFoundException { + int n = rtclasses.length; + CtClass[] ctclasses = new CtClass[n]; + for (int i = 0; i < n; ++i) + ctclasses[i] = toCtClass(rtclasses[i]); + + return ctclasses; + } + + /* ms must not be an array of CtMethod. To invoke a method ms[i] + * on a server, a client must send i to the server. + */ + private void addMethods(CtClass proxy, Method[] ms) + throws CannotCompileException, NotFoundException + { + CtMethod wmethod; + for (int i = 0; i < ms.length; ++i) { + Method m = ms[i]; + int mod = m.getModifiers(); + if (m.getDeclaringClass() != Object.class + && !Modifier.isFinal(mod)) + if (Modifier.isPublic(mod)) { + CtMethod body; + if (Modifier.isStatic(mod)) + body = forwardStaticMethod; + else + body = forwardMethod; + + wmethod + = CtNewMethod.wrapped(toCtClass(m.getReturnType()), + m.getName(), + toCtClass(m.getParameterTypes()), + exceptionForProxy, + body, + ConstParameter.integer(i), + proxy); + wmethod.setModifiers(mod); + proxy.addMethod(wmethod); + } + else if (!Modifier.isProtected(mod) + && !Modifier.isPrivate(mod)) + // if package method + throw new CannotCompileException( + "the methods must be public, protected, or private."); + } + } + + /** + * Adds a default constructor to the super classes. + */ + private void modifySuperclass(CtClass orgclass) + throws CannotCompileException, NotFoundException + { + CtClass superclazz; + for (;; orgclass = superclazz) { + superclazz = orgclass.getSuperclass(); + if (superclazz == null) + break; + + try { + superclazz.getDeclaredConstructor(null); + break; // the constructor with no arguments is found. + } + catch (NotFoundException e) { + } + + superclazz.addConstructor( + CtNewConstructor.defaultConstructor(superclazz)); + } + } +} diff --git a/src/main/javassist/tools/rmi/package.html b/src/main/javassist/tools/rmi/package.html new file mode 100644 index 0000000..5432a94 --- /dev/null +++ b/src/main/javassist/tools/rmi/package.html @@ -0,0 +1,16 @@ +<html> +<body> +Sample implementation of remote method invocation. + +<p>This package enables applets to access remote objects +running on the web server with regular Java syntax. +It is provided as a sample implementation with Javassist. +All the programs in this package uses only the regular +Javassist API; they never call any hidden methods. + +<p>The most significant class of this package is +<code>ObjectImporter</code>. +See the description of this class first. + +</body> +</html> diff --git a/src/main/javassist/tools/web/BadHttpRequest.java b/src/main/javassist/tools/web/BadHttpRequest.java new file mode 100644 index 0000000..0010203 --- /dev/null +++ b/src/main/javassist/tools/web/BadHttpRequest.java @@ -0,0 +1,34 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.tools.web; + +/** + * Thrown when receiving an invalid HTTP request. + */ +public class BadHttpRequest extends Exception { + private Exception e; + + public BadHttpRequest() { e = null; } + + public BadHttpRequest(Exception _e) { e = _e; } + + public String toString() { + if (e == null) + return super.toString(); + else + return e.toString(); + } +} diff --git a/src/main/javassist/tools/web/Viewer.java b/src/main/javassist/tools/web/Viewer.java new file mode 100644 index 0000000..de7afae --- /dev/null +++ b/src/main/javassist/tools/web/Viewer.java @@ -0,0 +1,208 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.tools.web; + +import java.io.*; +import java.net.*; + +/** + * A sample applet viewer. + * + * <p>This is a sort of applet viewer that can run any program even if + * the main class is not a subclass of <code>Applet</code>. + * This viewwer first calls <code>main()</code> in the main class. + * + * <p>To run, you should type: + * + * <ul><code>% java javassist.tools.web.Viewer <i>host port</i> Main arg1, ...</code></ul> + * + * <p>This command calls <code>Main.main()</code> with <code>arg1,...</code> + * All classes including <code>Main</code> are fetched from + * a server http://<i>host</i>:<i>port</i>. + * Only the class file for <code>Viewer</code> must exist + * on a local file system at the client side; even other + * <code>javassist.*</code> classes are not needed at the client side. + * <code>Viewer</code> uses only Java core API classes. + * + * <p>Note: since a <code>Viewer</code> object is a class loader, + * a program loaded by this object can call a method in <code>Viewer</code>. + * For example, you can write something like this: + * + * <ul><pre> + * Viewer v = (Viewer)this.getClass().getClassLoader(); + * String port = v.getPort(); + * </pre></ul> + * + */ +public class Viewer extends ClassLoader { + private String server; + private int port; + + /** + * Starts a program. + */ + public static void main(String[] args) throws Throwable { + if (args.length >= 3) { + Viewer cl = new Viewer(args[0], Integer.parseInt(args[1])); + String[] args2 = new String[args.length - 3]; + System.arraycopy(args, 3, args2, 0, args.length - 3); + cl.run(args[2], args2); + } + else + System.err.println( + "Usage: java javassist.tools.web.Viewer <host> <port> class [args ...]"); + } + + /** + * Constructs a viewer. + * + * @param host server name + * @param p port number + */ + public Viewer(String host, int p) { + server = host; + port = p; + } + + /** + * Returns the server name. + */ + public String getServer() { return server; } + + /** + * Returns the port number. + */ + public int getPort() { return port; } + + /** + * Invokes main() in the class specified by <code>classname</code>. + * + * @param classname executed class + * @param args the arguments passed to <code>main()</code>. + */ + public void run(String classname, String[] args) + throws Throwable + { + Class c = loadClass(classname); + try { + c.getDeclaredMethod("main", new Class[] { String[].class }) + .invoke(null, new Object[] { args }); + } + catch (java.lang.reflect.InvocationTargetException e) { + throw e.getTargetException(); + } + } + + /** + * Requests the class loader to load a class. + */ + protected synchronized Class loadClass(String name, boolean resolve) + throws ClassNotFoundException + { + Class c = findLoadedClass(name); + if (c == null) + c = findClass(name); + + if (c == null) + throw new ClassNotFoundException(name); + + if (resolve) + resolveClass(c); + + return c; + } + + /** + * Finds the specified class. The implementation in this class + * fetches the class from the http server. If the class is + * either <code>java.*</code>, <code>javax.*</code>, or + * <code>Viewer</code>, then it is loaded by the parent class + * loader. + * + * <p>This method can be overridden by a subclass of + * <code>Viewer</code>. + */ + protected Class findClass(String name) throws ClassNotFoundException { + Class c = null; + if (name.startsWith("java.") || name.startsWith("javax.") + || name.equals("javassist.tools.web.Viewer")) + c = findSystemClass(name); + + if (c == null) + try { + byte[] b = fetchClass(name); + if (b != null) + c = defineClass(name, b, 0, b.length); + } + catch (Exception e) { + } + + return c; + } + + /** + * Fetches the class file of the specified class from the http + * server. + */ + protected byte[] fetchClass(String classname) throws Exception + { + byte[] b; + URL url = new URL("http", server, port, + "/" + classname.replace('.', '/') + ".class"); + URLConnection con = url.openConnection(); + con.connect(); + int size = con.getContentLength(); + InputStream s = con.getInputStream(); + if (size <= 0) + b = readStream(s); + else { + b = new byte[size]; + int len = 0; + do { + int n = s.read(b, len, size - len); + if (n < 0) { + s.close(); + throw new IOException("the stream was closed: " + + classname); + } + len += n; + } while (len < size); + } + + s.close(); + return b; + } + + private byte[] readStream(InputStream fin) throws IOException { + byte[] buf = new byte[4096]; + int size = 0; + int len = 0; + do { + size += len; + if (buf.length - size <= 0) { + byte[] newbuf = new byte[buf.length * 2]; + System.arraycopy(buf, 0, newbuf, 0, size); + buf = newbuf; + } + + len = fin.read(buf, size, buf.length - size); + } while (len >= 0); + + byte[] result = new byte[size]; + System.arraycopy(buf, 0, result, 0, size); + return result; + } +} diff --git a/src/main/javassist/tools/web/Webserver.java b/src/main/javassist/tools/web/Webserver.java new file mode 100644 index 0000000..952d56d --- /dev/null +++ b/src/main/javassist/tools/web/Webserver.java @@ -0,0 +1,407 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.tools.web; + +import java.net.*; +import java.io.*; +import java.util.Date; +import javassist.*; + +/** + * A web server for running sample programs. + * + * <p>This enables a Java program to instrument class files loaded by + * web browsers for applets. Since the (standard) security manager + * does not allow an applet to create and use a class loader, + * instrumenting class files must be done by this web server. + * + * <p><b>Note:</b> although this class is included in the Javassist API, + * it is provided as a sample implementation of the web server using + * Javassist. Especially, there might be security flaws in this server. + * Please use this with YOUR OWN RISK. + */ +public class Webserver { + private ServerSocket socket; + private ClassPool classPool; + protected Translator translator; + + private final static byte[] endofline = { 0x0d, 0x0a }; + + private final static int typeHtml = 1; + private final static int typeClass = 2; + private final static int typeGif = 3; + private final static int typeJpeg = 4; + private final static int typeText = 5; + + /** + * If this field is not null, the class files taken from + * <code>ClassPool</code> are written out under the directory + * specified by this field. The directory name must not end + * with a directory separator. + */ + public String debugDir = null; + + /** + * The top directory of html (and .gif, .class, ...) files. + * It must end with the directory separator such as "/". + * (For portability, "/" should be used as the directory separator. + * Javassist automatically translates "/" into a platform-dependent + * character.) + * If this field is null, the top directory is the current one where + * the JVM is running. + * + * <p>If the given URL indicates a class file and the class file + * is not found under the directory specified by this variable, + * then <code>Class.getResourceAsStream()</code> is called + * for searching the Java class paths. + */ + public String htmlfileBase = null; + + /** + * Starts a web server. + * The port number is specified by the first argument. + */ + public static void main(String[] args) throws IOException { + if (args.length == 1) { + Webserver web = new Webserver(args[0]); + web.run(); + } + else + System.err.println( + "Usage: java javassist.tools.web.Webserver <port number>"); + } + + /** + * Constructs a web server. + * + * @param port port number + */ + public Webserver(String port) throws IOException { + this(Integer.parseInt(port)); + } + + /** + * Constructs a web server. + * + * @param port port number + */ + public Webserver(int port) throws IOException { + socket = new ServerSocket(port); + classPool = null; + translator = null; + } + + /** + * Requests the web server to use the specified + * <code>ClassPool</code> object for obtaining a class file. + */ + public void setClassPool(ClassPool loader) { + classPool = loader; + } + + /** + * Adds a translator, which is called whenever a client requests + * a class file. + * + * @param cp the <code>ClassPool</code> object for obtaining + * a class file. + * @param t a translator. + */ + public void addTranslator(ClassPool cp, Translator t) + throws NotFoundException, CannotCompileException + { + classPool = cp; + translator = t; + t.start(classPool); + } + + /** + * Closes the socket. + */ + public void end() throws IOException { + socket.close(); + } + + /** + * Prints a log message. + */ + public void logging(String msg) { + System.out.println(msg); + } + + /** + * Prints a log message. + */ + public void logging(String msg1, String msg2) { + System.out.print(msg1); + System.out.print(" "); + System.out.println(msg2); + } + + /** + * Prints a log message. + */ + public void logging(String msg1, String msg2, String msg3) { + System.out.print(msg1); + System.out.print(" "); + System.out.print(msg2); + System.out.print(" "); + System.out.println(msg3); + } + + /** + * Prints a log message with indentation. + */ + public void logging2(String msg) { + System.out.print(" "); + System.out.println(msg); + } + + /** + * Begins the HTTP service. + */ + public void run() { + System.err.println("ready to service..."); + for (;;) + try { + ServiceThread th = new ServiceThread(this, socket.accept()); + th.start(); + } + catch (IOException e) { + logging(e.toString()); + } + } + + final void process(Socket clnt) throws IOException { + InputStream in = new BufferedInputStream(clnt.getInputStream()); + String cmd = readLine(in); + logging(clnt.getInetAddress().getHostName(), + new Date().toString(), cmd); + while (skipLine(in) > 0){ + } + + OutputStream out = new BufferedOutputStream(clnt.getOutputStream()); + try { + doReply(in, out, cmd); + } + catch (BadHttpRequest e) { + replyError(out, e); + } + + out.flush(); + in.close(); + out.close(); + clnt.close(); + } + + private String readLine(InputStream in) throws IOException { + StringBuffer buf = new StringBuffer(); + int c; + while ((c = in.read()) >= 0 && c != 0x0d) + buf.append((char)c); + + in.read(); /* skip 0x0a (LF) */ + return buf.toString(); + } + + private int skipLine(InputStream in) throws IOException { + int c; + int len = 0; + while ((c = in.read()) >= 0 && c != 0x0d) + ++len; + + in.read(); /* skip 0x0a (LF) */ + return len; + } + + /** + * Proceses a HTTP request from a client. + * + * @param out the output stream to a client + * @param cmd the command received from a client + */ + public void doReply(InputStream in, OutputStream out, String cmd) + throws IOException, BadHttpRequest + { + int len; + int fileType; + String filename, urlName; + + if (cmd.startsWith("GET /")) + filename = urlName = cmd.substring(5, cmd.indexOf(' ', 5)); + else + throw new BadHttpRequest(); + + if (filename.endsWith(".class")) + fileType = typeClass; + else if (filename.endsWith(".html") || filename.endsWith(".htm")) + fileType = typeHtml; + else if (filename.endsWith(".gif")) + fileType = typeGif; + else if (filename.endsWith(".jpg")) + fileType = typeJpeg; + else + fileType = typeText; // or textUnknown + + len = filename.length(); + if (fileType == typeClass + && letUsersSendClassfile(out, filename, len)) + return; + + checkFilename(filename, len); + if (htmlfileBase != null) + filename = htmlfileBase + filename; + + if (File.separatorChar != '/') + filename = filename.replace('/', File.separatorChar); + + File file = new File(filename); + if (file.canRead()) { + sendHeader(out, file.length(), fileType); + FileInputStream fin = new FileInputStream(file); + byte[] filebuffer = new byte[4096]; + for (;;) { + len = fin.read(filebuffer); + if (len <= 0) + break; + else + out.write(filebuffer, 0, len); + } + + fin.close(); + return; + } + + // If the file is not found under the html-file directory, + // then Class.getResourceAsStream() is tried. + + if (fileType == typeClass) { + InputStream fin + = getClass().getResourceAsStream("/" + urlName); + if (fin != null) { + ByteArrayOutputStream barray = new ByteArrayOutputStream(); + byte[] filebuffer = new byte[4096]; + for (;;) { + len = fin.read(filebuffer); + if (len <= 0) + break; + else + barray.write(filebuffer, 0, len); + } + + byte[] classfile = barray.toByteArray(); + sendHeader(out, classfile.length, typeClass); + out.write(classfile); + fin.close(); + return; + } + } + + throw new BadHttpRequest(); + } + + private void checkFilename(String filename, int len) + throws BadHttpRequest + { + for (int i = 0; i < len; ++i) { + char c = filename.charAt(i); + if (!Character.isJavaIdentifierPart(c) && c != '.' && c != '/') + throw new BadHttpRequest(); + } + + if (filename.indexOf("..") >= 0) + throw new BadHttpRequest(); + } + + private boolean letUsersSendClassfile(OutputStream out, + String filename, int length) + throws IOException, BadHttpRequest + { + if (classPool == null) + return false; + + byte[] classfile; + String classname + = filename.substring(0, length - 6).replace('/', '.'); + try { + if (translator != null) + translator.onLoad(classPool, classname); + + CtClass c = classPool.get(classname); + classfile = c.toBytecode(); + if (debugDir != null) + c.writeFile(debugDir); + } + catch (Exception e) { + throw new BadHttpRequest(e); + } + + sendHeader(out, classfile.length, typeClass); + out.write(classfile); + return true; + } + + private void sendHeader(OutputStream out, long dataLength, int filetype) + throws IOException + { + out.write("HTTP/1.0 200 OK".getBytes()); + out.write(endofline); + out.write("Content-Length: ".getBytes()); + out.write(Long.toString(dataLength).getBytes()); + out.write(endofline); + if (filetype == typeClass) + out.write("Content-Type: application/octet-stream".getBytes()); + else if (filetype == typeHtml) + out.write("Content-Type: text/html".getBytes()); + else if (filetype == typeGif) + out.write("Content-Type: image/gif".getBytes()); + else if (filetype == typeJpeg) + out.write("Content-Type: image/jpg".getBytes()); + else if (filetype == typeText) + out.write("Content-Type: text/plain".getBytes()); + + out.write(endofline); + out.write(endofline); + } + + private void replyError(OutputStream out, BadHttpRequest e) + throws IOException + { + logging2("bad request: " + e.toString()); + out.write("HTTP/1.0 400 Bad Request".getBytes()); + out.write(endofline); + out.write(endofline); + out.write("<H1>Bad Request</H1>".getBytes()); + } +} + +class ServiceThread extends Thread { + Webserver web; + Socket sock; + + public ServiceThread(Webserver w, Socket s) { + web = w; + sock = s; + } + + public void run() { + try { + web.process(sock); + } + catch (IOException e) { + } + } +} diff --git a/src/main/javassist/tools/web/package.html b/src/main/javassist/tools/web/package.html new file mode 100644 index 0000000..0c7fb45 --- /dev/null +++ b/src/main/javassist/tools/web/package.html @@ -0,0 +1,7 @@ +<html> +<body> +Simple web server for running sample code. + +<p>This package provides a simple web server for sample packages. +</body> +</html> diff --git a/src/main/javassist/util/HotSwapper.java b/src/main/javassist/util/HotSwapper.java new file mode 100644 index 0000000..3536adf --- /dev/null +++ b/src/main/javassist/util/HotSwapper.java @@ -0,0 +1,252 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.util; + +import com.sun.jdi.*; +import com.sun.jdi.connect.*; +import com.sun.jdi.event.*; +import com.sun.jdi.request.*; +import java.io.*; +import java.util.*; + +class Trigger { + void doSwap() {} +} + +/** + * A utility class for dynamically reloading a class by + * the Java Platform Debugger Architecture (JPDA), or <it>HotSwap</code>. + * It works only with JDK 1.4 and later. + * + * <p><b>Note:</b> The new definition of the reloaded class must declare + * the same set of methods and fields as the original definition. The + * schema change between the original and new definitions is not allowed + * by the JPDA. + * + * <p>To use this class, the JVM must be launched with the following + * command line options: + * + * <ul> + * <p>For Java 1.4,<br> + * <pre>java -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8000</pre> + * <p>For Java 5,<br> + * <pre>java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8000</pre> + * </ul> + * + * <p>Note that 8000 is the port number used by <code>HotSwapper</code>. + * Any port number can be specified. Since <code>HotSwapper</code> does not + * launch another JVM for running a target application, this port number + * is used only for inter-thread communication. + * + * <p>Furthermore, <code>JAVA_HOME/lib/tools.jar</code> must be included + * in the class path. + * + * <p>Using <code>HotSwapper</code> is easy. See the following example: + * + * <ul><pre> + * CtClass clazz = ... + * byte[] classFile = clazz.toBytecode(); + * HotSwapper hs = new HostSwapper(8000); // 8000 is a port number. + * hs.reload("Test", classFile); + * </pre></ul> + * + * <p><code>reload()</code> + * first unload the <code>Test</code> class and load a new version of + * the <code>Test</code> class. + * <code>classFile</code> is a byte array containing the new contents of + * the class file for the <code>Test</code> class. The developers can + * repatedly call <code>reload()</code> on the same <code>HotSwapper</code> + * object so that they can reload a number of classes. + * + * @since 3.1 + */ +public class HotSwapper { + private VirtualMachine jvm; + private MethodEntryRequest request; + private Map newClassFiles; + + private Trigger trigger; + + private static final String HOST_NAME = "localhost"; + private static final String TRIGGER_NAME = Trigger.class.getName(); + + /** + * Connects to the JVM. + * + * @param port the port number used for the connection to the JVM. + */ + public HotSwapper(int port) + throws IOException, IllegalConnectorArgumentsException + { + this(Integer.toString(port)); + } + + /** + * Connects to the JVM. + * + * @param port the port number used for the connection to the JVM. + */ + public HotSwapper(String port) + throws IOException, IllegalConnectorArgumentsException + { + jvm = null; + request = null; + newClassFiles = null; + trigger = new Trigger(); + AttachingConnector connector + = (AttachingConnector)findConnector("com.sun.jdi.SocketAttach"); + + Map arguments = connector.defaultArguments(); + ((Connector.Argument)arguments.get("hostname")).setValue(HOST_NAME); + ((Connector.Argument)arguments.get("port")).setValue(port); + jvm = connector.attach(arguments); + EventRequestManager manager = jvm.eventRequestManager(); + request = methodEntryRequests(manager, TRIGGER_NAME); + } + + private Connector findConnector(String connector) throws IOException { + List connectors = Bootstrap.virtualMachineManager().allConnectors(); + Iterator iter = connectors.iterator(); + while (iter.hasNext()) { + Connector con = (Connector)iter.next(); + if (con.name().equals(connector)) { + return con; + } + } + + throw new IOException("Not found: " + connector); + } + + private static MethodEntryRequest methodEntryRequests( + EventRequestManager manager, + String classpattern) { + MethodEntryRequest mereq = manager.createMethodEntryRequest(); + mereq.addClassFilter(classpattern); + mereq.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD); + return mereq; + } + + /* Stops triggering a hotswapper when reload() is called. + */ + private void deleteEventRequest(EventRequestManager manager, + MethodEntryRequest request) { + manager.deleteEventRequest(request); + } + + /** + * Reloads a class. + * + * @param className the fully-qualified class name. + * @param classFile the contents of the class file. + */ + public void reload(String className, byte[] classFile) { + ReferenceType classtype = toRefType(className); + Map map = new HashMap(); + map.put(classtype, classFile); + reload2(map, className); + } + + /** + * Reloads a class. + * + * @param classFiles a map between fully-qualified class names + * and class files. The type of the class names + * is <code>String</code> and the type of the + * class files is <code>byte[]</code>. + */ + public void reload(Map classFiles) { + Set set = classFiles.entrySet(); + Iterator it = set.iterator(); + Map map = new HashMap(); + String className = null; + while (it.hasNext()) { + Map.Entry e = (Map.Entry)it.next(); + className = (String)e.getKey(); + map.put(toRefType(className), e.getValue()); + } + + if (className != null) + reload2(map, className + " etc."); + } + + private ReferenceType toRefType(String className) { + List list = jvm.classesByName(className); + if (list == null || list.isEmpty()) + throw new RuntimeException("no such class: " + className); + else + return (ReferenceType)list.get(0); + } + + private void reload2(Map map, String msg) { + synchronized (trigger) { + startDaemon(); + newClassFiles = map; + request.enable(); + trigger.doSwap(); + request.disable(); + Map ncf = newClassFiles; + if (ncf != null) { + newClassFiles = null; + throw new RuntimeException("failed to reload: " + msg); + } + } + } + + private void startDaemon() { + new Thread() { + private void errorMsg(Throwable e) { + System.err.print("Exception in thread \"HotSwap\" "); + e.printStackTrace(System.err); + } + + public void run() { + EventSet events = null; + try { + events = waitEvent(); + EventIterator iter = events.eventIterator(); + while (iter.hasNext()) { + Event event = iter.nextEvent(); + if (event instanceof MethodEntryEvent) { + hotswap(); + break; + } + } + } + catch (Throwable e) { + errorMsg(e); + } + try { + if (events != null) + events.resume(); + } + catch (Throwable e) { + errorMsg(e); + } + } + }.start(); + } + + EventSet waitEvent() throws InterruptedException { + EventQueue queue = jvm.eventQueue(); + return queue.remove(); + } + + void hotswap() { + Map map = newClassFiles; + jvm.redefineClasses(map); + newClassFiles = null; + } +} diff --git a/src/main/javassist/util/package.html b/src/main/javassist/util/package.html new file mode 100644 index 0000000..349d996 --- /dev/null +++ b/src/main/javassist/util/package.html @@ -0,0 +1,5 @@ +<html> +<body> +Utility classes. +</body> +</html> diff --git a/src/main/javassist/util/proxy/FactoryHelper.java b/src/main/javassist/util/proxy/FactoryHelper.java new file mode 100644 index 0000000..50d1944 --- /dev/null +++ b/src/main/javassist/util/proxy/FactoryHelper.java @@ -0,0 +1,236 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.util.proxy; + +import java.lang.reflect.Method; +import java.io.BufferedOutputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.security.ProtectionDomain; + +import javassist.CannotCompileException; +import javassist.bytecode.ClassFile; + +/** + * A helper class for implementing <code>ProxyFactory</code>. + * The users of <code>ProxyFactory</code> do not have to see this class. + * + * @see ProxyFactory + */ +public class FactoryHelper { + private static java.lang.reflect.Method defineClass1, defineClass2; + + static { + try { + Class cl = Class.forName("java.lang.ClassLoader"); + defineClass1 = SecurityActions.getDeclaredMethod( + cl, + "defineClass", + new Class[] { String.class, byte[].class, + int.class, int.class }); + + defineClass2 = SecurityActions.getDeclaredMethod( + cl, + "defineClass", + new Class[] { String.class, byte[].class, + int.class, int.class, ProtectionDomain.class }); + } + catch (Exception e) { + throw new RuntimeException("cannot initialize"); + } + } + + /** + * Returns an index for accessing arrays in this class. + * + * @throws RuntimeException if a given type is not a primitive type. + */ + public static final int typeIndex(Class type) { + Class[] list = primitiveTypes; + int n = list.length; + for (int i = 0; i < n; i++) + if (list[i] == type) + return i; + + throw new RuntimeException("bad type:" + type.getName()); + } + + /** + * <code>Class</code> objects representing primitive types. + */ + public static final Class[] primitiveTypes = { + Boolean.TYPE, Byte.TYPE, Character.TYPE, Short.TYPE, Integer.TYPE, + Long.TYPE, Float.TYPE, Double.TYPE, Void.TYPE + }; + + /** + * The fully-qualified names of wrapper classes for primitive types. + */ + public static final String[] wrapperTypes = { + "java.lang.Boolean", "java.lang.Byte", "java.lang.Character", + "java.lang.Short", "java.lang.Integer", "java.lang.Long", + "java.lang.Float", "java.lang.Double", "java.lang.Void" + }; + + /** + * The descriptors of the constructors of wrapper classes. + */ + public static final String[] wrapperDesc = { + "(Z)V", "(B)V", "(C)V", "(S)V", "(I)V", "(J)V", + "(F)V", "(D)V" + }; + + /** + * The names of methods for obtaining a primitive value + * from a wrapper object. For example, <code>intValue()</code> + * is such a method for obtaining an integer value from a + * <code>java.lang.Integer</code> object. + */ + public static final String[] unwarpMethods = { + "booleanValue", "byteValue", "charValue", "shortValue", + "intValue", "longValue", "floatValue", "doubleValue" + }; + + /** + * The descriptors of the unwrapping methods contained + * in <code>unwrapMethods</code>. + */ + public static final String[] unwrapDesc = { + "()Z", "()B", "()C", "()S", "()I", "()J", "()F", "()D" + }; + + /** + * The data size of primitive types. <code>long</code> + * and <code>double</code> are 2; the others are 1. + */ + public static final int[] dataSize = { + 1, 1, 1, 1, 1, 2, 1, 2 + }; + + /** + * Loads a class file by a given class loader. + * This method uses a default protection domain for the class + * but it may not work with a security manager or a sigend jar file. + * + * @see #toClass(ClassFile,ClassLoader,ProtectionDomain) + */ + public static Class toClass(ClassFile cf, ClassLoader loader) + throws CannotCompileException + { + return toClass(cf, loader, null); + } + + /** + * Loads a class file by a given class loader. + * + * @param domain if it is null, a default domain is used. + * @since 3.3 + */ + public static Class toClass(ClassFile cf, ClassLoader loader, ProtectionDomain domain) + throws CannotCompileException + { + try { + byte[] b = toBytecode(cf); + Method method; + Object[] args; + if (domain == null) { + method = defineClass1; + args = new Object[] { cf.getName(), b, new Integer(0), + new Integer(b.length) }; + } + else { + method = defineClass2; + args = new Object[] { cf.getName(), b, new Integer(0), + new Integer(b.length), domain }; + } + + return toClass2(method, loader, args); + } + catch (RuntimeException e) { + throw e; + } + catch (java.lang.reflect.InvocationTargetException e) { + throw new CannotCompileException(e.getTargetException()); + } + catch (Exception e) { + throw new CannotCompileException(e); + } + } + + private static synchronized Class toClass2(Method method, + ClassLoader loader, Object[] args) + throws Exception + { + SecurityActions.setAccessible(method, true); + Class clazz = (Class)method.invoke(loader, args); + SecurityActions.setAccessible(method, false); + return clazz; + } + + private static byte[] toBytecode(ClassFile cf) throws IOException { + ByteArrayOutputStream barray = new ByteArrayOutputStream(); + DataOutputStream out = new DataOutputStream(barray); + try { + cf.write(out); + } + finally { + out.close(); + } + + return barray.toByteArray(); + } + + /** + * Writes a class file. + */ + public static void writeFile(ClassFile cf, String directoryName) + throws CannotCompileException { + try { + writeFile0(cf, directoryName); + } + catch (IOException e) { + throw new CannotCompileException(e); + } + } + + private static void writeFile0(ClassFile cf, String directoryName) + throws CannotCompileException, IOException { + String classname = cf.getName(); + String filename = directoryName + File.separatorChar + + classname.replace('.', File.separatorChar) + ".class"; + int pos = filename.lastIndexOf(File.separatorChar); + if (pos > 0) { + String dir = filename.substring(0, pos); + if (!dir.equals(".")) + new File(dir).mkdirs(); + } + + DataOutputStream out = new DataOutputStream(new BufferedOutputStream( + new FileOutputStream(filename))); + try { + cf.write(out); + } + catch (IOException e) { + throw e; + } + finally { + out.close(); + } + } +} diff --git a/src/main/javassist/util/proxy/MethodFilter.java b/src/main/javassist/util/proxy/MethodFilter.java new file mode 100644 index 0000000..76084ff --- /dev/null +++ b/src/main/javassist/util/proxy/MethodFilter.java @@ -0,0 +1,30 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.util.proxy; + +import java.lang.reflect.Method; + +/** + * Selector of the methods implemented by a handler. + * + * @see ProxyFactory#setFilter(MethodFilter) + */ +public interface MethodFilter { + /** + * Returns true if the given method is implemented by a handler. + */ + boolean isHandled(Method m); +} diff --git a/src/main/javassist/util/proxy/MethodHandler.java b/src/main/javassist/util/proxy/MethodHandler.java new file mode 100644 index 0000000..2bb32cc --- /dev/null +++ b/src/main/javassist/util/proxy/MethodHandler.java @@ -0,0 +1,48 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.util.proxy; + +import java.lang.reflect.Method; + +/** + * The interface implemented by the invocation handler of a proxy + * instance. + * + * @see ProxyFactory#setHandler(MethodHandler) + */ +public interface MethodHandler { + /** + * Is called when a method is invoked on a proxy instance associated + * with this handler. This method must process that method invocation. + * + * @param self the proxy instance. + * @param thisMethod the overridden method declared in the super + * class or interface. + * @param proceed the forwarder method for invoking the overridden + * method. It is null if the overridden mehtod is + * abstract or declared in the interface. + * @param args an array of objects containing the values of + * the arguments passed in the method invocation + * on the proxy instance. If a parameter type is + * a primitive type, the type of the array element + * is a wrapper class. + * @return the resulting value of the method invocation. + * + * @throws Throwable if the method invocation fails. + */ + Object invoke(Object self, Method thisMethod, Method proceed, + Object[] args) throws Throwable; +} diff --git a/src/main/javassist/util/proxy/ProxyFactory.java b/src/main/javassist/util/proxy/ProxyFactory.java new file mode 100644 index 0000000..0750950 --- /dev/null +++ b/src/main/javassist/util/proxy/ProxyFactory.java @@ -0,0 +1,1334 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.util.proxy; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Constructor; +import java.lang.reflect.Member; +import java.lang.reflect.Modifier; +import java.security.ProtectionDomain; +import java.util.*; +import java.lang.ref.WeakReference; + +import javassist.CannotCompileException; +import javassist.bytecode.*; + +/* + * This class is implemented only with the lower-level API of Javassist. + * This design decision is for maximizing performance. + */ + +/** + * Factory of dynamic proxy classes. + * + * <p>This factory generates a class that extends the given super class and implements + * the given interfaces. The calls of the methods inherited from the super class are + * forwarded and then <code>invoke()</code> is called on the method handler + * associated with instances of the generated class. The calls of the methods from + * the interfaces are also forwarded to the method handler. + * + * <p>For example, if the following code is executed, + * + * <ul><pre> + * ProxyFactory f = new ProxyFactory(); + * f.setSuperclass(Foo.class); + * f.setFilter(new MethodFilter() { + * public boolean isHandled(Method m) { + * // ignore finalize() + * return !m.getName().equals("finalize"); + * } + * }); + * Class c = f.createClass(); + * MethodHandler mi = new MethodHandler() { + * public Object invoke(Object self, Method m, Method proceed, + * Object[] args) throws Throwable { + * System.out.println("Name: " + m.getName()); + * return proceed.invoke(self, args); // execute the original method. + * } + * }; + * Foo foo = (Foo)c.newInstance(); + * ((ProxyObject)foo).setHandler(mi); + * </pre></ul> + * + * <p>Then, the following method call will be forwarded to MethodHandler + * <code>mi</code> and prints a message before executing the originally called method + * <code>bar()</code> in <code>Foo</code>. + * + * <ul><pre> + * foo.bar(); + * </pre></ul> + * + * <p>The last three lines of the code shown above can be replaced with a call to + * the helper method <code>create</code>, which generates a proxy class, instantiates + * it, and sets the method handler of the instance: + * + * <ul><pre> + * : + * Foo foo = (Foo)f.create(new Class[0], new Object[0], mi); + * </pre></ul> + * + * <p>To change the method handler during runtime, + * execute the following code: + * + * <ul><pre> + * MethodHandler mi = ... ; // alternative handler + * ((ProxyObject)foo).setHandler(mi); + * </pre></ul> + * + * <p> If setHandler is never called for a proxy instance then it will + * employ the default handler which proceeds by invoking the original method. + * The behaviour of the default handler is identical to the following + * handler: + * + * <ul><pre> + * class EmptyHandler implements MethodHandler { + * public Object invoke(Object self, Method m, + * Method proceed, Object[] args) throws Exception { + * return proceed.invoke(self, args); + * } + * } + * </pre></ul> + * + * <p>A proxy factory caches and reuses proxy classes by default. It is possible to reset + * this default globally by setting static field {@link ProxyFactory#useCache} to false. + * Caching may also be configured for a specific factory by calling instance method + * {@link ProxyFactory#setUseCache(boolean)}. It is strongly recommended that new clients + * of class ProxyFactory enable caching. Failure to do so may lead to exhaustion of + * the heap memory area used to store classes. + * + * <p>Caching is automatically disabled for any given proxy factory if deprecated instance + * method {@link ProxyFactory#setHandler(MethodHandler)} is called. This method was + * used to specify a default handler which newly created proxy classes should install + * when they create their instances. It is only retained to provide backward compatibility + * with previous releases of javassist. Unfortunately,this legacy behaviour makes caching + * and reuse of proxy classes impossible. The current programming model expects javassist + * clients to set the handler of a proxy instance explicitly by calling method + * {@link ProxyObject#setHandler(MethodHandler)} as shown in the sample code above. New + * clients are strongly recommended to use this model rather than calling + * {@link ProxyFactory#setHandler(MethodHandler)}. + * + * <p>A proxy object generated by <code>ProxyFactory</code> is serializable + * if its super class or any of its interfaces implement <code>java.io.Serializable</code>. + * However, a serialized proxy object may not be compatible with future releases. + * The serialization support should be used for short-term storage or RMI. + * + * <p>For compatibility with older releases serialization of proxy objects is implemented by + * adding a writeReplace method to the proxy class. This allows a proxy to be serialized + * to a conventional {@link java.io.ObjectOutputStream} and deserialized from a corresponding + * {@link java.io.ObjectInputStream}. However this method suffers from several problems, the most + * notable one being that it fails to serialize state inherited from the proxy's superclass. + * <p> + * An alternative method of serializing proxy objects is available which fixes these problems. It + * requires inhibiting generation of the writeReplace method and instead using instances of + * {@link javassist.util.proxy.ProxyObjectOutputStream} and {@link javassist.util.proxy.ProxyObjectInputStream} + * (which are subclasses of {@link java.io.ObjectOutputStream} and {@link java.io.ObjectInputStream}) + * to serialize and deserialize, respectively, the proxy. These streams recognise javassist proxies and ensure + * that they are serialized and deserialized without the need for the proxy class to implement special methods + * such as writeReplace. Generation of the writeReplace method can be disabled globally by setting static field + * {@link ProxyFactory#useWriteReplace} to false. Alternatively, it may be + * configured per factory by calling instance method {@link ProxyFactory#setUseWriteReplace(boolean)}. + * + * @see MethodHandler + * @since 3.1 + * @author Muga Nishizawa + * @author Shigeru Chiba + * @author Andrew Dinn + */ +public class ProxyFactory { + private Class superClass; + private Class[] interfaces; + private MethodFilter methodFilter; + private MethodHandler handler; // retained for legacy usage + private List signatureMethods; + private byte[] signature; + private String classname; + private String basename; + private String superName; + private Class thisClass; + /** + * per factory setting initialised from current setting for useCache but able to be reset before each create call + */ + private boolean factoryUseCache; + /** + * per factory setting initialised from current setting for useWriteReplace but able to be reset before each create call + */ + private boolean factoryWriteReplace; + + + /** + * If the value of this variable is not null, the class file of + * the generated proxy class is written under the directory specified + * by this variable. For example, if the value is + * <code>"."</code>, then the class file is written under the current + * directory. This method is for debugging. + * + * <p>The default value is null. + */ + public String writeDirectory; + + private static final Class OBJECT_TYPE = Object.class; + + private static final String HOLDER = "_methods_"; + private static final String HOLDER_TYPE = "[Ljava/lang/reflect/Method;"; + private static final String FILTER_SIGNATURE_FIELD = "_filter_signature"; + private static final String FILTER_SIGNATURE_TYPE = "[B"; + private static final String HANDLER = "handler"; + private static final String NULL_INTERCEPTOR_HOLDER = "javassist.util.proxy.RuntimeSupport"; + private static final String DEFAULT_INTERCEPTOR = "default_interceptor"; + private static final String HANDLER_TYPE + = 'L' + MethodHandler.class.getName().replace('.', '/') + ';'; + private static final String HANDLER_SETTER = "setHandler"; + private static final String HANDLER_SETTER_TYPE = "(" + HANDLER_TYPE + ")V"; + + private static final String HANDLER_GETTER = "getHandler"; + private static final String HANDLER_GETTER_TYPE = "()" + HANDLER_TYPE; + + private static final String SERIAL_VERSION_UID_FIELD = "serialVersionUID"; + private static final String SERIAL_VERSION_UID_TYPE = "J"; + private static final int SERIAL_VERSION_UID_VALUE = -1; + + /** + * If true, a generated proxy class is cached and it will be reused + * when generating the proxy class with the same properties is requested. + * The default value is true. + * + * Note that this value merely specifies the initial setting employed by any newly created + * proxy factory. The factory setting may be overwritten by calling factory instance method + * {@link #setUseCache(boolean)} + * + * @since 3.4 + */ + public static volatile boolean useCache = true; + + /** + * If true, a generated proxy class will implement method writeReplace enabling + * serialization of its proxies to a conventional ObjectOutputStream. this (default) + * setting retains the old javassist behaviour which has the advantage that it + * retains compatibility with older releases and requires no extra work on the part + * of the client performing the serialization. However, it has the disadvantage that + * state inherited from the superclasses of the proxy is lost during serialization. + * if false then serialization/deserialization of the proxy instances will preserve + * all fields. However, serialization must be performed via a {@link ProxyObjectOutputStream} + * and deserialization must be via {@link ProxyObjectInputStream}. Any attempt to serialize + * proxies whose class was created with useWriteReplace set to false via a normal + * {@link java.io.ObjectOutputStream} will fail. + * + * Note that this value merely specifies the initial setting employed by any newly created + * proxy factory. The factory setting may be overwritten by calling factory instance method + * {@link #setUseWriteReplace(boolean)} + * + * @since 3.4 + */ + public static volatile boolean useWriteReplace = true; + + /* + * methods allowing individual factory settings for factoryUseCache and factoryWriteReplace to be reset + */ + + /** + * test whether this factory uses the proxy cache + * @return true if this factory uses the proxy cache otherwise false + */ + public boolean isUseCache() + { + return factoryUseCache; + } + + /** + * configure whether this factory should use the proxy cache + * @param useCache true if this factory should use the proxy cache and false if it should not use the cache + * @throws RuntimeException if a default interceptor has been set for the factory + */ + public void setUseCache(boolean useCache) + { + // we cannot allow caching to be used if the factory is configured to install a default interceptor + // field into generated classes + if (handler != null && useCache) { + throw new RuntimeException("caching cannot be enabled if the factory default interceptor has been set"); + } + factoryUseCache = useCache; + } + + /** + * test whether this factory installs a writeReplace method in created classes + * @return true if this factory installs a writeReplace method in created classes otherwise false + */ + public boolean isUseWriteReplace() + { + return factoryWriteReplace; + } + + /** + * configure whether this factory should add a writeReplace method to created classes + * @param useWriteReplace true if this factory should add a writeReplace method to created classes and false if it + * should not add a writeReplace method + */ + public void setUseWriteReplace(boolean useWriteReplace) + { + factoryWriteReplace = useWriteReplace; + } + + private static WeakHashMap proxyCache = new WeakHashMap(); + + /** + * determine if a class is a javassist proxy class + * @param cl + * @return true if the class is a javassist proxy class otherwise false + */ + public static boolean isProxyClass(Class cl) + { + // all proxies implement ProxyObject. nothing else should. + return (ProxyObject.class.isAssignableFrom(cl)); + } + + /** + * used to store details of a specific proxy class in the second tier of the proxy cache. this entry + * will be located in a hashmap keyed by the unique identifying name of the proxy class. the hashmap is + * located in a weak hashmap keyed by the classloader common to all proxy classes in the second tier map. + */ + static class ProxyDetails { + /** + * the unique signature of any method filter whose behaviour will be met by this class. each bit in + * the byte array is set if the filter redirects the corresponding super or interface method and clear + * if it does not redirect it. + */ + byte[] signature; + /** + * a hexadecimal string representation of the signature bit sequence. this string also forms part + * of the proxy class name. + */ + WeakReference proxyClass; + /** + * a flag which is true this class employs writeReplace to perform serialization of its instances + * and false if serialization must employ of a ProxyObjectOutputStream and ProxyObjectInputStream + */ + boolean isUseWriteReplace; + + ProxyDetails(byte[] signature, Class proxyClass, boolean isUseWriteReplace) + { + this.signature = signature; + this.proxyClass = new WeakReference(proxyClass); + this.isUseWriteReplace = isUseWriteReplace; + } + } + + /** + * Constructs a factory of proxy class. + */ + public ProxyFactory() { + superClass = null; + interfaces = null; + methodFilter = null; + handler = null; + signature = null; + signatureMethods = null; + thisClass = null; + writeDirectory = null; + factoryUseCache = useCache; + factoryWriteReplace = useWriteReplace; + } + + /** + * Sets the super class of a proxy class. + */ + public void setSuperclass(Class clazz) { + superClass = clazz; + // force recompute of signature + signature = null; + } + + /** + * Obtains the super class set by <code>setSuperclass()</code>. + * + * @since 3.4 + */ + public Class getSuperclass() { return superClass; } + + /** + * Sets the interfaces of a proxy class. + */ + public void setInterfaces(Class[] ifs) { + interfaces = ifs; + // force recompute of signature + signature = null; + } + + /** + * Obtains the interfaces set by <code>setInterfaces</code>. + * + * @since 3.4 + */ + public Class[] getInterfaces() { return interfaces; } + + /** + * Sets a filter that selects the methods that will be controlled by a handler. + */ + public void setFilter(MethodFilter mf) { + methodFilter = mf; + // force recompute of signature + signature = null; + } + + /** + * Generates a proxy class using the current filter. + */ + public Class createClass() { + if (signature == null) { + computeSignature(methodFilter); + } + return createClass1(); + } + + /** + * Generates a proxy class using the supplied filter. + */ + public Class createClass(MethodFilter filter) { + computeSignature(filter); + return createClass1(); + } + + /** + * Generates a proxy class with a specific signature. + * access is package local so ProxyObjectInputStream can use this + * @param signature + * @return + */ + Class createClass(byte[] signature) + { + installSignature(signature); + return createClass1(); + } + + private Class createClass1() { + if (thisClass == null) { + ClassLoader cl = getClassLoader(); + synchronized (proxyCache) { + if (factoryUseCache) + createClass2(cl); + else + createClass3(cl); + } + } + + // don't retain any unwanted references + Class result = thisClass; + thisClass = null; + + return result; + } + + private static char[] hexDigits = + { '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; + + public String getKey(Class superClass, Class[] interfaces, byte[] signature, boolean useWriteReplace) + { + StringBuffer sbuf = new StringBuffer(); + if (superClass != null){ + sbuf.append(superClass.getName()); + } + sbuf.append(":"); + for (int i = 0; i < interfaces.length; i++) { + sbuf.append(interfaces[i].getName()); + sbuf.append(":"); + } + for (int i = 0; i < signature.length; i++) { + byte b = signature[i]; + int lo = b & 0xf; + int hi = (b >> 4) & 0xf; + sbuf.append(hexDigits[lo]); + sbuf.append(hexDigits[hi]); + } + if (useWriteReplace) { + sbuf.append(":w"); + } + + return sbuf.toString(); + } + + private void createClass2(ClassLoader cl) { + String key = getKey(superClass, interfaces, signature, factoryWriteReplace); + /* + * Excessive concurrency causes a large memory footprint and slows the + * execution speed down (with JDK 1.5). Thus, we use a jumbo lock for + * reducing concrrency. + */ + // synchronized (proxyCache) { + HashMap cacheForTheLoader = (HashMap)proxyCache.get(cl); + ProxyDetails details; + if (cacheForTheLoader == null) { + cacheForTheLoader = new HashMap(); + proxyCache.put(cl, cacheForTheLoader); + } + details = (ProxyDetails)cacheForTheLoader.get(key); + if (details != null) { + WeakReference reference = details.proxyClass; + thisClass = (Class)reference.get(); + if (thisClass != null) { + return; + } + } + createClass3(cl); + details = new ProxyDetails(signature, thisClass, factoryWriteReplace); + cacheForTheLoader.put(key, details); + // } + } + + private void createClass3(ClassLoader cl) { + // we need a new class so we need a new class name + allocateClassName(); + + try { + ClassFile cf = make(); + if (writeDirectory != null) + FactoryHelper.writeFile(cf, writeDirectory); + + thisClass = FactoryHelper.toClass(cf, cl, getDomain()); + setField(FILTER_SIGNATURE_FIELD, signature); + // legacy behaviour : we only set the default interceptor static field if we are not using the cache + if (!factoryUseCache) { + setField(DEFAULT_INTERCEPTOR, handler); + } + } + catch (CannotCompileException e) { + throw new RuntimeException(e.getMessage(), e); + } + + } + + private void setField(String fieldName, Object value) { + if (thisClass != null && value != null) + try { + Field f = thisClass.getField(fieldName); + SecurityActions.setAccessible(f, true); + f.set(null, value); + SecurityActions.setAccessible(f, false); + } + catch (Exception e) { + throw new RuntimeException(e); + } + } + + static byte[] getFilterSignature(Class clazz) { + return (byte[])getField(clazz, FILTER_SIGNATURE_FIELD); + } + + private static Object getField(Class clazz, String fieldName) { + try { + Field f = clazz.getField(fieldName); + f.setAccessible(true); + Object value = f.get(null); + f.setAccessible(false); + return value; + } + catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * A provider of class loaders. + * + * @see #classLoaderProvider + * @since 3.4 + */ + public static interface ClassLoaderProvider { + /** + * Returns a class loader. + * + * @param pf a proxy factory that is going to obtain a class loader. + */ + public ClassLoader get(ProxyFactory pf); + } + + /** + * A provider used by <code>createClass()</code> for obtaining + * a class loader. + * <code>get()</code> on this <code>ClassLoaderProvider</code> object + * is called to obtain a class loader. + * + * <p>The value of this field can be updated for changing the default + * implementation. + * + * <p>Example: + * <ul><pre> + * ProxyFactory.classLoaderProvider = new ProxyFactory.ClassLoaderProvider() { + * public ClassLoader get(ProxyFactory pf) { + * return Thread.currentThread().getContextClassLoader(); + * } + * }; + * </pre></ul> + * + * @since 3.4 + */ + public static ClassLoaderProvider classLoaderProvider + = new ClassLoaderProvider() { + public ClassLoader get(ProxyFactory pf) { + return pf.getClassLoader0(); + } + }; + + protected ClassLoader getClassLoader() { + return classLoaderProvider.get(this); + } + + protected ClassLoader getClassLoader0() { + ClassLoader loader = null; + if (superClass != null && !superClass.getName().equals("java.lang.Object")) + loader = superClass.getClassLoader(); + else if (interfaces != null && interfaces.length > 0) + loader = interfaces[0].getClassLoader(); + + if (loader == null) { + loader = getClass().getClassLoader(); + // In case javassist is in the endorsed dir + if (loader == null) { + loader = Thread.currentThread().getContextClassLoader(); + if (loader == null) + loader = ClassLoader.getSystemClassLoader(); + } + } + + return loader; + } + + protected ProtectionDomain getDomain() { + Class clazz; + if (superClass != null && !superClass.getName().equals("java.lang.Object")) + clazz = superClass; + else if (interfaces != null && interfaces.length > 0) + clazz = interfaces[0]; + else + clazz = this.getClass(); + + return clazz.getProtectionDomain(); + } + + /** + * Creates a proxy class and returns an instance of that class. + * + * @param paramTypes parameter types for a constructor. + * @param args arguments passed to a constructor. + * @param mh the method handler for the proxy class. + * @since 3.4 + */ + public Object create(Class[] paramTypes, Object[] args, MethodHandler mh) + throws NoSuchMethodException, IllegalArgumentException, + InstantiationException, IllegalAccessException, InvocationTargetException + { + Object obj = create(paramTypes, args); + ((ProxyObject)obj).setHandler(mh); + return obj; + } + + /** + * Creates a proxy class and returns an instance of that class. + * + * @param paramTypes parameter types for a constructor. + * @param args arguments passed to a constructor. + */ + public Object create(Class[] paramTypes, Object[] args) + throws NoSuchMethodException, IllegalArgumentException, + InstantiationException, IllegalAccessException, InvocationTargetException + { + Class c = createClass(); + Constructor cons = c.getConstructor(paramTypes); + return cons.newInstance(args); + } + + /** + * Sets the default invocation handler. This invocation handler is shared + * among all the instances of a proxy class unless another is explicitly + * specified. + * @deprecated since 3.12 + * use of this method is incompatible with proxy class caching. + * instead clients should call method {@link ProxyObject#setHandler(MethodHandler)} to set the handler + * for each newly created proxy instance. + * calling this method will automatically disable caching of classes created by the proxy factory. + */ + public void setHandler(MethodHandler mi) { + // if we were using the cache and the handler is non-null then we must stop caching + if (factoryUseCache && mi != null) { + factoryUseCache = false; + // clear any currently held class so we don't try to reuse it or set its handler field + thisClass = null; + } + handler = mi; + // this retains the behaviour of the old code which resets any class we were holding on to + // this is probably not what is wanted + setField(DEFAULT_INTERCEPTOR, handler); + } + + private static int counter = 0; + + private static synchronized String makeProxyName(String classname) { + return classname + "_$$_javassist_" + counter++; + } + + private ClassFile make() throws CannotCompileException { + ClassFile cf = new ClassFile(false, classname, superName); + cf.setAccessFlags(AccessFlag.PUBLIC); + setInterfaces(cf, interfaces); + ConstPool pool = cf.getConstPool(); + + // legacy: we only add the static field for the default interceptor if caching is disabled + if (!factoryUseCache) { + FieldInfo finfo = new FieldInfo(pool, DEFAULT_INTERCEPTOR, HANDLER_TYPE); + finfo.setAccessFlags(AccessFlag.PUBLIC | AccessFlag.STATIC); + cf.addField(finfo); + } + + // handler is per instance + FieldInfo finfo2 = new FieldInfo(pool, HANDLER, HANDLER_TYPE); + finfo2.setAccessFlags(AccessFlag.PRIVATE); + cf.addField(finfo2); + + // filter signature is per class + FieldInfo finfo3 = new FieldInfo(pool, FILTER_SIGNATURE_FIELD, FILTER_SIGNATURE_TYPE); + finfo3.setAccessFlags(AccessFlag.PUBLIC | AccessFlag.STATIC); + cf.addField(finfo3); + + // the proxy class serial uid must always be a fixed value + FieldInfo finfo4 = new FieldInfo(pool, SERIAL_VERSION_UID_FIELD, SERIAL_VERSION_UID_TYPE); + finfo4.setAccessFlags(AccessFlag.PUBLIC | AccessFlag.STATIC| AccessFlag.FINAL); + cf.addField(finfo4); + + // HashMap allMethods = getMethods(superClass, interfaces); + // int size = allMethods.size(); + makeConstructors(classname, cf, pool, classname); + int s = overrideMethods(cf, pool, classname); + addMethodsHolder(cf, pool, classname, s); + addSetter(classname, cf, pool); + addGetter(classname, cf, pool); + + if (factoryWriteReplace) { + try { + cf.addMethod(makeWriteReplace(pool)); + } + catch (DuplicateMemberException e) { + // writeReplace() is already declared in the super class/interfaces. + } + } + + thisClass = null; + return cf; + } + + private void checkClassAndSuperName() + { + if (interfaces == null) + interfaces = new Class[0]; + + if (superClass == null) { + superClass = OBJECT_TYPE; + superName = superClass.getName(); + basename = interfaces.length == 0 ? superName + : interfaces[0].getName(); + } else { + superName = superClass.getName(); + basename = superName; + } + + if (Modifier.isFinal(superClass.getModifiers())) + throw new RuntimeException(superName + " is final"); + + if (basename.startsWith("java.")) + basename = "org.javassist.tmp." + basename; + } + + private void allocateClassName() + { + classname = makeProxyName(basename); + } + + private static Comparator sorter = new Comparator() { + + public int compare(Object o1, Object o2) { + Map.Entry e1 = (Map.Entry)o1; + Map.Entry e2 = (Map.Entry)o2; + String key1 = (String)e1.getKey(); + String key2 = (String)e2.getKey(); + return key1.compareTo(key2); + } + }; + + private void makeSortedMethodList() + { + checkClassAndSuperName(); + + HashMap allMethods = getMethods(superClass, interfaces); + signatureMethods = new ArrayList(allMethods.entrySet()); + Collections.sort(signatureMethods, sorter); + } + + private void computeSignature(MethodFilter filter) // throws CannotCompileException + { + makeSortedMethodList(); + + int l = signatureMethods.size(); + int maxBytes = ((l + 7) >> 3); + signature = new byte[maxBytes]; + for (int idx = 0; idx < l; idx++) + { + Map.Entry e = (Map.Entry)signatureMethods.get(idx); + Method m = (Method)e.getValue(); + int mod = m.getModifiers(); + if (!Modifier.isFinal(mod) && !Modifier.isStatic(mod) + && isVisible(mod, basename, m) && (filter == null || filter.isHandled(m))) { + setBit(signature, idx); + } + } + } + + private void installSignature(byte[] signature) // throws CannotCompileException + { + makeSortedMethodList(); + + int l = signatureMethods.size(); + int maxBytes = ((l + 7) >> 3); + if (signature.length != maxBytes) { + throw new RuntimeException("invalid filter signature length for deserialized proxy class"); + } + + this.signature = signature; + } + + private boolean testBit(byte[] signature, int idx) + { + int byteIdx = idx >> 3; + if (byteIdx > signature.length) { + return false; + } else { + int bitIdx = idx & 0x7; + int mask = 0x1 << bitIdx; + int sigByte = signature[byteIdx]; + return ((sigByte & mask) != 0); + } + } + + private void setBit(byte[] signature, int idx) + { + int byteIdx = idx >> 3; + if (byteIdx < signature.length) { + int bitIdx = idx & 0x7; + int mask = 0x1 << bitIdx; + int sigByte = signature[byteIdx]; + signature[byteIdx] = (byte)(sigByte | mask); + } + } + + private static void setInterfaces(ClassFile cf, Class[] interfaces) { + String setterIntf = ProxyObject.class.getName(); + String[] list; + if (interfaces == null || interfaces.length == 0) + list = new String[] { setterIntf }; + else { + list = new String[interfaces.length + 1]; + for (int i = 0; i < interfaces.length; i++) + list[i] = interfaces[i].getName(); + + list[interfaces.length] = setterIntf; + } + + cf.setInterfaces(list); + } + + private static void addMethodsHolder(ClassFile cf, ConstPool cp, + String classname, int size) + throws CannotCompileException + { + FieldInfo finfo = new FieldInfo(cp, HOLDER, HOLDER_TYPE); + finfo.setAccessFlags(AccessFlag.PRIVATE | AccessFlag.STATIC); + cf.addField(finfo); + MethodInfo minfo = new MethodInfo(cp, "<clinit>", "()V"); + minfo.setAccessFlags(AccessFlag.STATIC); + Bytecode code = new Bytecode(cp, 0, 0); + code.addIconst(size * 2); + code.addAnewarray("java.lang.reflect.Method"); + code.addPutstatic(classname, HOLDER, HOLDER_TYPE); + // also need to set serial version uid + code.addLconst(-1L); + code.addPutstatic(classname, SERIAL_VERSION_UID_FIELD, SERIAL_VERSION_UID_TYPE); + code.addOpcode(Bytecode.RETURN); + minfo.setCodeAttribute(code.toCodeAttribute()); + cf.addMethod(minfo); + } + + private static void addSetter(String classname, ClassFile cf, ConstPool cp) + throws CannotCompileException + { + MethodInfo minfo = new MethodInfo(cp, HANDLER_SETTER, + HANDLER_SETTER_TYPE); + minfo.setAccessFlags(AccessFlag.PUBLIC); + Bytecode code = new Bytecode(cp, 2, 2); + code.addAload(0); + code.addAload(1); + code.addPutfield(classname, HANDLER, HANDLER_TYPE); + code.addOpcode(Bytecode.RETURN); + minfo.setCodeAttribute(code.toCodeAttribute()); + cf.addMethod(minfo); + } + + private static void addGetter(String classname, ClassFile cf, ConstPool cp) + throws CannotCompileException + { + MethodInfo minfo = new MethodInfo(cp, HANDLER_GETTER, + HANDLER_GETTER_TYPE); + minfo.setAccessFlags(AccessFlag.PUBLIC); + Bytecode code = new Bytecode(cp, 1, 1); + code.addAload(0); + code.addGetfield(classname, HANDLER, HANDLER_TYPE); + code.addOpcode(Bytecode.ARETURN); + minfo.setCodeAttribute(code.toCodeAttribute()); + cf.addMethod(minfo); + } + + private int overrideMethods(ClassFile cf, ConstPool cp, String className) + throws CannotCompileException + { + String prefix = makeUniqueName("_d", signatureMethods); + Iterator it = signatureMethods.iterator(); + int index = 0; + while (it.hasNext()) { + Map.Entry e = (Map.Entry)it.next(); + String key = (String)e.getKey(); + Method meth = (Method)e.getValue(); + int mod = meth.getModifiers(); + if (testBit(signature, index)) { + override(className, meth, prefix, index, + keyToDesc(key), cf, cp); + } + index++; + } + + return index; + } + + private void override(String thisClassname, Method meth, String prefix, + int index, String desc, ClassFile cf, ConstPool cp) + throws CannotCompileException + { + Class declClass = meth.getDeclaringClass(); + String delegatorName = prefix + index + meth.getName(); + if (Modifier.isAbstract(meth.getModifiers())) + delegatorName = null; + else { + MethodInfo delegator + = makeDelegator(meth, desc, cp, declClass, delegatorName); + // delegator is not a bridge method. See Sec. 15.12.4.5 of JLS 3rd Ed. + delegator.setAccessFlags(delegator.getAccessFlags() & ~AccessFlag.BRIDGE); + cf.addMethod(delegator); + } + + MethodInfo forwarder + = makeForwarder(thisClassname, meth, desc, cp, declClass, + delegatorName, index); + cf.addMethod(forwarder); + } + + private void makeConstructors(String thisClassName, ClassFile cf, + ConstPool cp, String classname) throws CannotCompileException + { + Constructor[] cons = SecurityActions.getDeclaredConstructors(superClass); + // legacy: if we are not caching then we need to initialise the default handler + boolean doHandlerInit = !factoryUseCache; + for (int i = 0; i < cons.length; i++) { + Constructor c = cons[i]; + int mod = c.getModifiers(); + if (!Modifier.isFinal(mod) && !Modifier.isPrivate(mod) + && isVisible(mod, basename, c)) { + MethodInfo m = makeConstructor(thisClassName, c, cp, superClass, doHandlerInit); + cf.addMethod(m); + } + } + } + + private static String makeUniqueName(String name, List sortedMethods) { + if (makeUniqueName0(name, sortedMethods.iterator())) + return name; + + for (int i = 100; i < 999; i++) { + String s = name + i; + if (makeUniqueName0(s, sortedMethods.iterator())) + return s; + } + + throw new RuntimeException("cannot make a unique method name"); + } + + private static boolean makeUniqueName0(String name, Iterator it) { + while (it.hasNext()) { + Map.Entry e = (Map.Entry)it.next(); + String key = (String)e.getKey(); + if (key.startsWith(name)) + return false; + } + + return true; + } + + /** + * Returns true if the method is visible from the package. + * + * @param mod the modifiers of the method. + */ + private static boolean isVisible(int mod, String from, Member meth) { + if ((mod & Modifier.PRIVATE) != 0) + return false; + else if ((mod & (Modifier.PUBLIC | Modifier.PROTECTED)) != 0) + return true; + else { + String p = getPackageName(from); + String q = getPackageName(meth.getDeclaringClass().getName()); + if (p == null) + return q == null; + else + return p.equals(q); + } + } + + private static String getPackageName(String name) { + int i = name.lastIndexOf('.'); + if (i < 0) + return null; + else + return name.substring(0, i); + } + + private static HashMap getMethods(Class superClass, Class[] interfaceTypes) { + HashMap hash = new HashMap(); + for (int i = 0; i < interfaceTypes.length; i++) + getMethods(hash, interfaceTypes[i]); + + getMethods(hash, superClass); + return hash; + } + + private static void getMethods(HashMap hash, Class clazz) { + Class[] ifs = clazz.getInterfaces(); + for (int i = 0; i < ifs.length; i++) + getMethods(hash, ifs[i]); + + Class parent = clazz.getSuperclass(); + if (parent != null) + getMethods(hash, parent); + + Method[] methods = SecurityActions.getDeclaredMethods(clazz); + for (int i = 0; i < methods.length; i++) + if (!Modifier.isPrivate(methods[i].getModifiers())) { + Method m = methods[i]; + String key = m.getName() + ':' + RuntimeSupport.makeDescriptor(m); + // JIRA JASSIST-85 + // put the method to the cache, retrieve previous definition (if any) + Method oldMethod = (Method)hash.put(key, methods[i]); + + // check if visibility has been reduced + if (null != oldMethod && Modifier.isPublic(oldMethod.getModifiers()) + && !Modifier.isPublic(methods[i].getModifiers()) ) { + // we tried to overwrite a public definition with a non-public definition, + // use the old definition instead. + hash.put(key, oldMethod); + } + } + } + + private static String keyToDesc(String key) { + return key.substring(key.indexOf(':') + 1); + } + + private static MethodInfo makeConstructor(String thisClassName, Constructor cons, + ConstPool cp, Class superClass, boolean doHandlerInit) { + String desc = RuntimeSupport.makeDescriptor(cons.getParameterTypes(), + Void.TYPE); + MethodInfo minfo = new MethodInfo(cp, "<init>", desc); + minfo.setAccessFlags(Modifier.PUBLIC); // cons.getModifiers() & ~Modifier.NATIVE + setThrows(minfo, cp, cons.getExceptionTypes()); + Bytecode code = new Bytecode(cp, 0, 0); + + // legacy: if we are not using caching then we initialise the instance's handler + // from the class's static default interceptor and skip the next few instructions if + // it is non-null + if (doHandlerInit) { + code.addAload(0); + code.addGetstatic(thisClassName, DEFAULT_INTERCEPTOR, HANDLER_TYPE); + code.addPutfield(thisClassName, HANDLER, HANDLER_TYPE); + code.addGetstatic(thisClassName, DEFAULT_INTERCEPTOR, HANDLER_TYPE); + code.addOpcode(Opcode.IFNONNULL); + code.addIndex(10); + } + // if caching is enabled then we don't have a handler to initialise so this else branch will install + // the handler located in the static field of class RuntimeSupport. + code.addAload(0); + code.addGetstatic(NULL_INTERCEPTOR_HOLDER, DEFAULT_INTERCEPTOR, HANDLER_TYPE); + code.addPutfield(thisClassName, HANDLER, HANDLER_TYPE); + int pc = code.currentPc(); + + code.addAload(0); + int s = addLoadParameters(code, cons.getParameterTypes(), 1); + code.addInvokespecial(superClass.getName(), "<init>", desc); + code.addOpcode(Opcode.RETURN); + code.setMaxLocals(s + 1); + CodeAttribute ca = code.toCodeAttribute(); + minfo.setCodeAttribute(ca); + + StackMapTable.Writer writer = new StackMapTable.Writer(32); + writer.sameFrame(pc); + ca.setAttribute(writer.toStackMapTable(cp)); + return minfo; + } + + private static MethodInfo makeDelegator(Method meth, String desc, + ConstPool cp, Class declClass, String delegatorName) { + MethodInfo delegator = new MethodInfo(cp, delegatorName, desc); + delegator.setAccessFlags(Modifier.FINAL | Modifier.PUBLIC + | (meth.getModifiers() & ~(Modifier.PRIVATE + | Modifier.PROTECTED + | Modifier.ABSTRACT + | Modifier.NATIVE + | Modifier.SYNCHRONIZED))); + setThrows(delegator, cp, meth); + Bytecode code = new Bytecode(cp, 0, 0); + code.addAload(0); + int s = addLoadParameters(code, meth.getParameterTypes(), 1); + code.addInvokespecial(declClass.getName(), meth.getName(), desc); + addReturn(code, meth.getReturnType()); + code.setMaxLocals(++s); + delegator.setCodeAttribute(code.toCodeAttribute()); + return delegator; + } + + /** + * @param delegatorName null if the original method is abstract. + */ + private static MethodInfo makeForwarder(String thisClassName, + Method meth, String desc, ConstPool cp, + Class declClass, String delegatorName, int index) { + MethodInfo forwarder = new MethodInfo(cp, meth.getName(), desc); + forwarder.setAccessFlags(Modifier.FINAL + | (meth.getModifiers() & ~(Modifier.ABSTRACT + | Modifier.NATIVE + | Modifier.SYNCHRONIZED))); + setThrows(forwarder, cp, meth); + int args = Descriptor.paramSize(desc); + Bytecode code = new Bytecode(cp, 0, args + 2); + /* + * if (methods[index * 2] == null) { + * methods[index * 2] + * = RuntimeSupport.findSuperMethod(this, <overridden name>, <desc>); + * methods[index * 2 + 1] + * = RuntimeSupport.findMethod(this, <delegator name>, <desc>); + * or = null // the original method is abstract. + * } + * return ($r)handler.invoke(this, methods[index * 2], + * methods[index * 2 + 1], $args); + */ + int origIndex = index * 2; + int delIndex = index * 2 + 1; + int arrayVar = args + 1; + code.addGetstatic(thisClassName, HOLDER, HOLDER_TYPE); + code.addAstore(arrayVar); + + callFind2Methods(code, meth.getName(), delegatorName, origIndex, desc, arrayVar); + + code.addAload(0); + code.addGetfield(thisClassName, HANDLER, HANDLER_TYPE); + code.addAload(0); + + code.addAload(arrayVar); + code.addIconst(origIndex); + code.addOpcode(Opcode.AALOAD); + + code.addAload(arrayVar); + code.addIconst(delIndex); + code.addOpcode(Opcode.AALOAD); + + makeParameterList(code, meth.getParameterTypes()); + code.addInvokeinterface(MethodHandler.class.getName(), "invoke", + "(Ljava/lang/Object;Ljava/lang/reflect/Method;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;", + 5); + Class retType = meth.getReturnType(); + addUnwrapper(code, retType); + addReturn(code, retType); + + CodeAttribute ca = code.toCodeAttribute(); + forwarder.setCodeAttribute(ca); + return forwarder; + } + + private static void setThrows(MethodInfo minfo, ConstPool cp, Method orig) { + Class[] exceptions = orig.getExceptionTypes(); + setThrows(minfo, cp, exceptions); + } + + private static void setThrows(MethodInfo minfo, ConstPool cp, + Class[] exceptions) { + if (exceptions.length == 0) + return; + + String[] list = new String[exceptions.length]; + for (int i = 0; i < exceptions.length; i++) + list[i] = exceptions[i].getName(); + + ExceptionsAttribute ea = new ExceptionsAttribute(cp); + ea.setExceptions(list); + minfo.setExceptionsAttribute(ea); + } + + private static int addLoadParameters(Bytecode code, Class[] params, + int offset) { + int stacksize = 0; + int n = params.length; + for (int i = 0; i < n; ++i) + stacksize += addLoad(code, stacksize + offset, params[i]); + + return stacksize; + } + + private static int addLoad(Bytecode code, int n, Class type) { + if (type.isPrimitive()) { + if (type == Long.TYPE) { + code.addLload(n); + return 2; + } + else if (type == Float.TYPE) + code.addFload(n); + else if (type == Double.TYPE) { + code.addDload(n); + return 2; + } + else + code.addIload(n); + } + else + code.addAload(n); + + return 1; + } + + private static int addReturn(Bytecode code, Class type) { + if (type.isPrimitive()) { + if (type == Long.TYPE) { + code.addOpcode(Opcode.LRETURN); + return 2; + } + else if (type == Float.TYPE) + code.addOpcode(Opcode.FRETURN); + else if (type == Double.TYPE) { + code.addOpcode(Opcode.DRETURN); + return 2; + } + else if (type == Void.TYPE) { + code.addOpcode(Opcode.RETURN); + return 0; + } + else + code.addOpcode(Opcode.IRETURN); + } + else + code.addOpcode(Opcode.ARETURN); + + return 1; + } + + private static void makeParameterList(Bytecode code, Class[] params) { + int regno = 1; + int n = params.length; + code.addIconst(n); + code.addAnewarray("java/lang/Object"); + for (int i = 0; i < n; i++) { + code.addOpcode(Opcode.DUP); + code.addIconst(i); + Class type = params[i]; + if (type.isPrimitive()) + regno = makeWrapper(code, type, regno); + else { + code.addAload(regno); + regno++; + } + + code.addOpcode(Opcode.AASTORE); + } + } + + private static int makeWrapper(Bytecode code, Class type, int regno) { + int index = FactoryHelper.typeIndex(type); + String wrapper = FactoryHelper.wrapperTypes[index]; + code.addNew(wrapper); + code.addOpcode(Opcode.DUP); + addLoad(code, regno, type); + code.addInvokespecial(wrapper, "<init>", + FactoryHelper.wrapperDesc[index]); + return regno + FactoryHelper.dataSize[index]; + } + + /** + * @param thisMethod might be null. + */ + private static void callFind2Methods(Bytecode code, String superMethod, String thisMethod, + int index, String desc, int arrayVar) { + String findClass = RuntimeSupport.class.getName(); + String findDesc + = "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;ILjava/lang/String;[Ljava/lang/reflect/Method;)V"; + + code.addAload(0); + code.addLdc(superMethod); + if (thisMethod == null) + code.addOpcode(Opcode.ACONST_NULL); + else + code.addLdc(thisMethod); + + code.addIconst(index); + code.addLdc(desc); + code.addAload(arrayVar); + code.addInvokestatic(findClass, "find2Methods", findDesc); + } + + private static void addUnwrapper(Bytecode code, Class type) { + if (type.isPrimitive()) { + if (type == Void.TYPE) + code.addOpcode(Opcode.POP); + else { + int index = FactoryHelper.typeIndex(type); + String wrapper = FactoryHelper.wrapperTypes[index]; + code.addCheckcast(wrapper); + code.addInvokevirtual(wrapper, + FactoryHelper.unwarpMethods[index], + FactoryHelper.unwrapDesc[index]); + } + } + else + code.addCheckcast(type.getName()); + } + + private static MethodInfo makeWriteReplace(ConstPool cp) { + MethodInfo minfo = new MethodInfo(cp, "writeReplace", "()Ljava/lang/Object;"); + String[] list = new String[1]; + list[0] = "java.io.ObjectStreamException"; + ExceptionsAttribute ea = new ExceptionsAttribute(cp); + ea.setExceptions(list); + minfo.setExceptionsAttribute(ea); + Bytecode code = new Bytecode(cp, 0, 1); + code.addAload(0); + code.addInvokestatic("javassist.util.proxy.RuntimeSupport", + "makeSerializedProxy", + "(Ljava/lang/Object;)Ljavassist/util/proxy/SerializedProxy;"); + code.addOpcode(Opcode.ARETURN); + minfo.setCodeAttribute(code.toCodeAttribute()); + return minfo; + } +} diff --git a/src/main/javassist/util/proxy/ProxyObject.java b/src/main/javassist/util/proxy/ProxyObject.java new file mode 100644 index 0000000..08febd6 --- /dev/null +++ b/src/main/javassist/util/proxy/ProxyObject.java @@ -0,0 +1,36 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.util.proxy; + +/** + * The interface implemented by proxy classes. + * + * @see ProxyFactory + */ +public interface ProxyObject { + /** + * Sets a handler. It can be used for changing handlers + * during runtime. + */ + void setHandler(MethodHandler mi); + + /** + * Get the handler. + * This can be used to access values of the underlying MethodHandler + * or to serialize it properly. + */ + MethodHandler getHandler(); +} diff --git a/src/main/javassist/util/proxy/ProxyObjectInputStream.java b/src/main/javassist/util/proxy/ProxyObjectInputStream.java new file mode 100644 index 0000000..62c5eea --- /dev/null +++ b/src/main/javassist/util/proxy/ProxyObjectInputStream.java @@ -0,0 +1,99 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2010 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.util.proxy; + +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectStreamClass; + +/** + * An input stream class which knows how to deserialize proxies created via {@link ProxyFactory} and + * serializedo via a {@link ProxyObjectOutputStream}. It must be used when deserialising proxies created + * from a proxy factory configured with {@link ProxyFactory#useWriteReplace} set to false. + * + * @author Andrew Dinn + */ +public class ProxyObjectInputStream extends ObjectInputStream +{ + /** + * create an input stream which can be used to deserialize an object graph which includes proxies created + * using class ProxyFactory. the classloader used to resolve proxy superclass and interface names + * read from the input stream will default to the current thread's context class loader or the system + * classloader if the context class loader is null. + * @param in + * @throws java.io.StreamCorruptedException whenever ObjectInputStream would also do so + * @throws IOException whenever ObjectInputStream would also do so + * @throws SecurityException whenever ObjectInputStream would also do so + * @throws NullPointerException if in is null + */ + public ProxyObjectInputStream(InputStream in) throws IOException + { + super(in); + loader = Thread.currentThread().getContextClassLoader(); + if (loader == null) { + loader = ClassLoader.getSystemClassLoader(); + } + } + + /** + * Reset the loader to be + * @param loader + */ + public void setClassLoader(ClassLoader loader) + { + if (loader != null) { + this.loader = loader; + } else { + loader = ClassLoader.getSystemClassLoader(); + } + } + + protected ObjectStreamClass readClassDescriptor() throws IOException, ClassNotFoundException { + boolean isProxy = readBoolean(); + if (isProxy) { + String name = (String)readObject(); + Class superClass = loader.loadClass(name); + int length = readInt(); + Class[] interfaces = new Class[length]; + for (int i = 0; i < length; i++) { + name = (String)readObject(); + interfaces[i] = loader.loadClass(name); + } + length = readInt(); + byte[] signature = new byte[length]; + read(signature); + ProxyFactory factory = new ProxyFactory(); + // we must always use the cache and never use writeReplace when using + // ProxyObjectOutputStream and ProxyObjectInputStream + factory.setUseCache(true); + factory.setUseWriteReplace(false); + factory.setSuperclass(superClass); + factory.setInterfaces(interfaces); + Class proxyClass = factory.createClass(signature); + return ObjectStreamClass.lookup(proxyClass); + } else { + return super.readClassDescriptor(); + } + } + + /** + * the loader to use to resolve classes for proxy superclass and interface names read + * from the stream. defaults to the context class loader of the thread which creates + * the input stream or the system class loader if the context class loader is null. + */ + private ClassLoader loader; +}
\ No newline at end of file diff --git a/src/main/javassist/util/proxy/ProxyObjectOutputStream.java b/src/main/javassist/util/proxy/ProxyObjectOutputStream.java new file mode 100644 index 0000000..b2f8bd4 --- /dev/null +++ b/src/main/javassist/util/proxy/ProxyObjectOutputStream.java @@ -0,0 +1,71 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2010 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.util.proxy; + +import java.io.IOException; +import java.io.ObjectOutputStream; +import java.io.ObjectStreamClass; +import java.io.OutputStream; + +/** + * An input stream class which knows how to serialize proxies created via {@link ProxyFactory}. It must + * be used when serialising proxies created from a proxy factory configured with + * {@link ProxyFactory#useWriteReplace} set to false. Subsequent deserialization of the serialized data + * must employ a {@link ProxyObjectInputStream} + * + * @author Andrew Dinn + */ +public class ProxyObjectOutputStream extends ObjectOutputStream +{ + /** + * create an output stream which can be used to serialize an object graph which includes proxies created + * using class ProxyFactory + * @param out + * @throws IOException whenever ObjectOutputStream would also do so + * @throws SecurityException whenever ObjectOutputStream would also do so + * @throws NullPointerException if out is null + */ + public ProxyObjectOutputStream(OutputStream out) throws IOException + { + super(out); + } + + protected void writeClassDescriptor(ObjectStreamClass desc) throws IOException { + Class cl = desc.forClass(); + if (ProxyFactory.isProxyClass(cl)) { + writeBoolean(true); + Class superClass = cl.getSuperclass(); + Class[] interfaces = cl.getInterfaces(); + byte[] signature = ProxyFactory.getFilterSignature(cl); + String name = superClass.getName(); + writeObject(name); + // we don't write the marker interface ProxyObject + writeInt(interfaces.length - 1); + for (int i = 0; i < interfaces.length; i++) { + Class interfaze = interfaces[i]; + if (interfaze != ProxyObject.class) { + name = interfaces[i].getName(); + writeObject(name); + } + } + writeInt(signature.length); + write(signature); + } else { + writeBoolean(false); + super.writeClassDescriptor(desc); + } + } +} diff --git a/src/main/javassist/util/proxy/RuntimeSupport.java b/src/main/javassist/util/proxy/RuntimeSupport.java new file mode 100644 index 0000000..817ab4c --- /dev/null +++ b/src/main/javassist/util/proxy/RuntimeSupport.java @@ -0,0 +1,211 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.util.proxy; + +import java.lang.reflect.Method; +import java.io.Serializable; + +/** + * Runtime support routines that the classes generated by ProxyFactory use. + * + * @see ProxyFactory + */ +public class RuntimeSupport { + /** + * A method handler that only executes a method. + */ + public static MethodHandler default_interceptor = new DefaultMethodHandler(); + + static class DefaultMethodHandler implements MethodHandler, Serializable { + public Object invoke(Object self, Method m, + Method proceed, Object[] args) + throws Exception + { + return proceed.invoke(self, args); + } + }; + + /** + * Finds two methods specified by the parameters and stores them + * into the given array. + * + * @throws RuntimeException if the methods are not found. + * @see javassist.util.proxy.ProxyFactory + */ + public static void find2Methods(Object self, String superMethod, + String thisMethod, int index, + String desc, java.lang.reflect.Method[] methods) + { + synchronized (methods) { + if (methods[index] == null) { + methods[index + 1] = thisMethod == null ? null + : findMethod(self, thisMethod, desc); + methods[index] = findSuperMethod(self, superMethod, desc); + } + } + } + + /** + * Finds a method with the given name and descriptor. + * It searches only the class of self. + * + * @throws RuntimeException if the method is not found. + */ + public static Method findMethod(Object self, String name, String desc) { + Method m = findMethod2(self.getClass(), name, desc); + if (m == null) + error(self, name, desc); + + return m; + } + + /** + * Finds a method that has the given name and descriptor and is declared + * in the super class. + * + * @throws RuntimeException if the method is not found. + */ + public static Method findSuperMethod(Object self, String name, String desc) { + Class clazz = self.getClass(); + Method m = findSuperMethod2(clazz.getSuperclass(), name, desc); + if (m == null) + m = searchInterfaces(clazz, name, desc); + + if (m == null) + error(self, name, desc); + + return m; + } + + private static void error(Object self, String name, String desc) { + throw new RuntimeException("not found " + name + ":" + desc + + " in " + self.getClass().getName()); + } + + private static Method findSuperMethod2(Class clazz, String name, String desc) { + Method m = findMethod2(clazz, name, desc); + if (m != null) + return m; + + Class superClass = clazz.getSuperclass(); + if (superClass != null) { + m = findSuperMethod2(superClass, name, desc); + if (m != null) + return m; + } + + return searchInterfaces(clazz, name, desc); + } + + private static Method searchInterfaces(Class clazz, String name, String desc) { + Method m = null; + Class[] interfaces = clazz.getInterfaces(); + for (int i = 0; i < interfaces.length; i++) { + m = findSuperMethod2(interfaces[i], name, desc); + if (m != null) + return m; + } + + return m; + } + + private static Method findMethod2(Class clazz, String name, String desc) { + Method[] methods = SecurityActions.getDeclaredMethods(clazz); + int n = methods.length; + for (int i = 0; i < n; i++) + if (methods[i].getName().equals(name) + && makeDescriptor(methods[i]).equals(desc)) + return methods[i]; + + return null; + } + + /** + * Makes a descriptor for a given method. + */ + public static String makeDescriptor(Method m) { + Class[] params = m.getParameterTypes(); + return makeDescriptor(params, m.getReturnType()); + } + + /** + * Makes a descriptor for a given method. + * + * @param params parameter types. + * @param retType return type. + */ + public static String makeDescriptor(Class[] params, Class retType) { + StringBuffer sbuf = new StringBuffer(); + sbuf.append('('); + for (int i = 0; i < params.length; i++) + makeDesc(sbuf, params[i]); + + sbuf.append(')'); + makeDesc(sbuf, retType); + return sbuf.toString(); + } + + private static void makeDesc(StringBuffer sbuf, Class type) { + if (type.isArray()) { + sbuf.append('['); + makeDesc(sbuf, type.getComponentType()); + } + else if (type.isPrimitive()) { + if (type == Void.TYPE) + sbuf.append('V'); + else if (type == Integer.TYPE) + sbuf.append('I'); + else if (type == Byte.TYPE) + sbuf.append('B'); + else if (type == Long.TYPE) + sbuf.append('J'); + else if (type == Double.TYPE) + sbuf.append('D'); + else if (type == Float.TYPE) + sbuf.append('F'); + else if (type == Character.TYPE) + sbuf.append('C'); + else if (type == Short.TYPE) + sbuf.append('S'); + else if (type == Boolean.TYPE) + sbuf.append('Z'); + else + throw new RuntimeException("bad type: " + type.getName()); + } + else + sbuf.append('L').append(type.getName().replace('.', '/')) + .append(';'); + } + + /** + * Converts a proxy object to an object that is writable to an + * object stream. This method is called by <code>writeReplace()</code> + * in a proxy class. + * + * @since 3.4 + */ + public static SerializedProxy makeSerializedProxy(Object proxy) + throws java.io.InvalidClassException + { + Class clazz = proxy.getClass(); + + MethodHandler methodHandler = null; + if (proxy instanceof ProxyObject) + methodHandler = ((ProxyObject)proxy).getHandler(); + + return new SerializedProxy(clazz, ProxyFactory.getFilterSignature(clazz), methodHandler); + } +} diff --git a/src/main/javassist/util/proxy/SecurityActions.java b/src/main/javassist/util/proxy/SecurityActions.java new file mode 100644 index 0000000..40741e4 --- /dev/null +++ b/src/main/javassist/util/proxy/SecurityActions.java @@ -0,0 +1,135 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ +package javassist.util.proxy; + +import java.lang.reflect.AccessibleObject; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; + +class SecurityActions { + static Method[] getDeclaredMethods(final Class clazz) { + if (System.getSecurityManager() == null) + return clazz.getDeclaredMethods(); + else { + return (Method[]) AccessController + .doPrivileged(new PrivilegedAction() { + public Object run() { + return clazz.getDeclaredMethods(); + } + }); + } + } + + static Constructor[] getDeclaredConstructors(final Class clazz) { + if (System.getSecurityManager() == null) + return clazz.getDeclaredConstructors(); + else { + return (Constructor[]) AccessController + .doPrivileged(new PrivilegedAction() { + public Object run() { + return clazz.getDeclaredConstructors(); + } + }); + } + } + + static Method getDeclaredMethod(final Class clazz, final String name, + final Class[] types) throws NoSuchMethodException { + if (System.getSecurityManager() == null) + return clazz.getDeclaredMethod(name, types); + else { + try { + return (Method) AccessController + .doPrivileged(new PrivilegedExceptionAction() { + public Object run() throws Exception { + return clazz.getDeclaredMethod(name, types); + } + }); + } + catch (PrivilegedActionException e) { + if (e.getCause() instanceof NoSuchMethodException) + throw (NoSuchMethodException) e.getCause(); + + throw new RuntimeException(e.getCause()); + } + } + } + + static Constructor getDeclaredConstructor(final Class clazz, + final Class[] types) + throws NoSuchMethodException + { + if (System.getSecurityManager() == null) + return clazz.getDeclaredConstructor(types); + else { + try { + return (Constructor) AccessController + .doPrivileged(new PrivilegedExceptionAction() { + public Object run() throws Exception { + return clazz.getDeclaredConstructor(types); + } + }); + } + catch (PrivilegedActionException e) { + if (e.getCause() instanceof NoSuchMethodException) + throw (NoSuchMethodException) e.getCause(); + + throw new RuntimeException(e.getCause()); + } + } + } + + static void setAccessible(final AccessibleObject ao, + final boolean accessible) { + if (System.getSecurityManager() == null) + ao.setAccessible(accessible); + else { + AccessController.doPrivileged(new PrivilegedAction() { + public Object run() { + ao.setAccessible(accessible); + return null; + } + }); + } + } + + static void set(final Field fld, final Object target, final Object value) + throws IllegalAccessException + { + if (System.getSecurityManager() == null) + fld.set(target, value); + else { + try { + AccessController.doPrivileged(new PrivilegedExceptionAction() { + public Object run() throws Exception { + fld.set(target, value); + return null; + } + }); + } + catch (PrivilegedActionException e) { + if (e.getCause() instanceof NoSuchMethodException) + throw (IllegalAccessException) e.getCause(); + + throw new RuntimeException(e.getCause()); + } + } + } +} diff --git a/src/main/javassist/util/proxy/SerializedProxy.java b/src/main/javassist/util/proxy/SerializedProxy.java new file mode 100644 index 0000000..cddfab4 --- /dev/null +++ b/src/main/javassist/util/proxy/SerializedProxy.java @@ -0,0 +1,97 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999-2007 Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.util.proxy; + +import java.io.Serializable; +import java.io.ObjectStreamException; +import java.security.AccessController; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; +import java.security.ProtectionDomain; + +/** + * A proxy object is converted into an instance of this class + * when it is written to an output stream. + * + * @see RuntimeSupport#makeSerializedProxy(Object) + */ +class SerializedProxy implements Serializable { + private String superClass; + private String[] interfaces; + private byte[] filterSignature; + private MethodHandler handler; + + SerializedProxy(Class proxy, byte[] sig, MethodHandler h) { + filterSignature = sig; + handler = h; + superClass = proxy.getSuperclass().getName(); + Class[] infs = proxy.getInterfaces(); + int n = infs.length; + interfaces = new String[n - 1]; + String setterInf = ProxyObject.class.getName(); + for (int i = 0; i < n; i++) { + String name = infs[i].getName(); + if (!name.equals(setterInf)) + interfaces[i] = name; + } + } + + /** + * Load class. + * + * @param className the class name + * @return loaded class + * @throws ClassNotFoundException for any error + */ + protected Class loadClass(final String className) throws ClassNotFoundException { + try { + return (Class)AccessController.doPrivileged(new PrivilegedExceptionAction(){ + public Object run() throws Exception{ + ClassLoader cl = Thread.currentThread().getContextClassLoader(); + return Class.forName(className, true, cl); + } + }); + } + catch (PrivilegedActionException pae) { + throw new RuntimeException("cannot load the class: " + className, pae.getException()); + } + } + + Object readResolve() throws ObjectStreamException { + try { + int n = interfaces.length; + Class[] infs = new Class[n]; + for (int i = 0; i < n; i++) + infs[i] = loadClass(interfaces[i]); + + ProxyFactory f = new ProxyFactory(); + f.setSuperclass(loadClass(superClass)); + f.setInterfaces(infs); + ProxyObject proxy = (ProxyObject)f.createClass(filterSignature).newInstance(); + proxy.setHandler(handler); + return proxy; + } + catch (ClassNotFoundException e) { + throw new java.io.InvalidClassException(e.getMessage()); + } + catch (InstantiationException e2) { + throw new java.io.InvalidObjectException(e2.getMessage()); + } + catch (IllegalAccessException e3) { + throw new java.io.InvalidClassException(e3.getMessage()); + } + } +} diff --git a/src/main/javassist/util/proxy/package.html b/src/main/javassist/util/proxy/package.html new file mode 100644 index 0000000..6c77804 --- /dev/null +++ b/src/main/javassist/util/proxy/package.html @@ -0,0 +1,6 @@ +<html> +<body> +Dynamic proxy (similar to <code>Enhancer</code> of <a href="http://cglib.sourceforge.net/">cglib</a>). +See <code>ProxyFactory</code> for more details. +</body> +</html> diff --git a/src/test/test/javassist/bytecode/analysis/AnalyzerTest.java b/src/test/test/javassist/bytecode/analysis/AnalyzerTest.java new file mode 100644 index 0000000..0c5a77e --- /dev/null +++ b/src/test/test/javassist/bytecode/analysis/AnalyzerTest.java @@ -0,0 +1,411 @@ +package test.javassist.bytecode.analysis; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Iterator; + +import javassist.ClassPool; +import javassist.CtClass; +import javassist.CtMethod; +import javassist.bytecode.AccessFlag; +import javassist.bytecode.BadBytecode; +import javassist.bytecode.Bytecode; +import javassist.bytecode.CodeIterator; +import javassist.bytecode.MethodInfo; +import javassist.bytecode.Opcode; +import javassist.bytecode.analysis.Analyzer; +import javassist.bytecode.analysis.Frame; +import javassist.bytecode.analysis.Type; +import junit.framework.TestCase; + +/** + * Tests Analyzer + * + * @author Jason T. Greene + */ +public class AnalyzerTest extends TestCase { + + public void testCommonSupperArray() throws Exception { + ClassPool pool = ClassPool.getDefault(); + CtClass clazz = pool.get(getClass().getName() + "$Dummy"); + CtMethod method = clazz.getDeclaredMethod("commonSuperArray"); + verifyArrayLoad(clazz, method, "java.lang.Number"); + } + + public void testCommonInterfaceArray() throws Exception { + ClassPool pool = ClassPool.getDefault(); + CtClass clazz = pool.get(getClass().getName() + "$Dummy"); + CtMethod method = clazz.getDeclaredMethod("commonInterfaceArray"); + verifyArrayLoad(clazz, method, "java.io.Serializable"); + } + + public void testSharedInterfaceAndSuperClass() throws Exception { + CtMethod method = ClassPool.getDefault().getMethod( + getClass().getName() + "$Dummy", "sharedInterfaceAndSuperClass"); + verifyReturn(method, "java.io.Serializable"); + + method = ClassPool.getDefault().getMethod( + getClass().getName() + "$Dummy", "sharedOffsetInterfaceAndSuperClass"); + verifyReturn(method, "java.io.Serializable"); + + method = ClassPool.getDefault().getMethod( + getClass().getName() + "$Dummy", "sharedSuperWithSharedInterface"); + verifyReturn(method, getClass().getName() + "$Dummy$A"); + } + + public void testArrayDifferentDims() throws Exception { + CtMethod method = ClassPool.getDefault().getMethod( + getClass().getName() + "$Dummy", "arrayDifferentDimensions1"); + verifyReturn(method, "java.lang.Cloneable[]"); + + method = ClassPool.getDefault().getMethod( + getClass().getName() + "$Dummy", "arrayDifferentDimensions2"); + verifyReturn(method, "java.lang.Object[][]"); + } + + public void testReusedLocalMerge() throws Exception { + CtMethod method = ClassPool.getDefault().getMethod( + getClass().getName() + "$Dummy", "reusedLocalMerge"); + + MethodInfo info = method.getMethodInfo2(); + Analyzer analyzer = new Analyzer(); + Frame[] frames = analyzer.analyze(method.getDeclaringClass(), info); + assertNotNull(frames); + int pos = findOpcode(info, Opcode.RETURN); + Frame frame = frames[pos]; + assertEquals("java.lang.Object", frame.getLocal(2).getCtClass().getName()); + } + + private static int findOpcode(MethodInfo info, int opcode) throws BadBytecode { + CodeIterator iter = info.getCodeAttribute().iterator(); + + // find return + int pos = 0; + while (iter.hasNext()) { + pos = iter.next(); + if (iter.byteAt(pos) == opcode) + break; + } + return pos; + } + + + private static void verifyReturn(CtMethod method, String expected) throws BadBytecode { + MethodInfo info = method.getMethodInfo2(); + CodeIterator iter = info.getCodeAttribute().iterator(); + + // find areturn + int pos = 0; + while (iter.hasNext()) { + pos = iter.next(); + if (iter.byteAt(pos) == Opcode.ARETURN) + break; + } + + Analyzer analyzer = new Analyzer(); + Frame[] frames = analyzer.analyze(method.getDeclaringClass(), info); + assertNotNull(frames); + Frame frame = frames[pos]; + assertEquals(expected, frame.peek().getCtClass().getName()); + } + + private static void verifyArrayLoad(CtClass clazz, CtMethod method, String component) + throws BadBytecode { + MethodInfo info = method.getMethodInfo2(); + CodeIterator iter = info.getCodeAttribute().iterator(); + + // find aaload + int pos = 0; + while (iter.hasNext()) { + pos = iter.next(); + if (iter.byteAt(pos) == Opcode.AALOAD) + break; + } + + Analyzer analyzer = new Analyzer(); + Frame[] frames = analyzer.analyze(clazz, info); + assertNotNull(frames); + Frame frame = frames[pos]; + assertNotNull(frame); + + Type type = frame.getStack(frame.getTopIndex() - 1); + assertEquals(component + "[]", type.getCtClass().getName()); + + pos = iter.next(); + frame = frames[pos]; + assertNotNull(frame); + + type = frame.getStack(frame.getTopIndex()); + assertEquals(component, type.getCtClass().getName()); + } + + private static void addJump(Bytecode code, int opcode, int pos) { + int current = code.currentPc(); + code.addOpcode(opcode); + code.addIndex(pos - current); + } + + public void testDeadCode() throws Exception { + CtMethod method = generateDeadCode(ClassPool.getDefault()); + Analyzer analyzer = new Analyzer(); + Frame[] frames = analyzer.analyze(method.getDeclaringClass(), method.getMethodInfo2()); + assertNotNull(frames); + assertNull(frames[4]); + assertNotNull(frames[5]); + verifyReturn(method, "java.lang.String"); + } + + public void testInvalidCode() throws Exception { + CtMethod method = generateInvalidCode(ClassPool.getDefault()); + Analyzer analyzer = new Analyzer(); + try { + analyzer.analyze(method.getDeclaringClass(), method.getMethodInfo2()); + } catch (BadBytecode e) { + return; + } + + fail("Invalid code should have triggered a BadBytecode exception"); + } + + public void testCodeFalloff() throws Exception { + CtMethod method = generateCodeFalloff(ClassPool.getDefault()); + Analyzer analyzer = new Analyzer(); + try { + analyzer.analyze(method.getDeclaringClass(), method.getMethodInfo2()); + } catch (BadBytecode e) { + return; + } + + fail("Code falloff should have triggered a BadBytecode exception"); + } + + public void testJsrMerge() throws Exception { + CtMethod method = generateJsrMerge(ClassPool.getDefault()); + Analyzer analyzer = new Analyzer(); + analyzer.analyze(method.getDeclaringClass(), method.getMethodInfo2()); + verifyReturn(method, "java.lang.String"); + } + + public void testJsrMerge2() throws Exception { + CtMethod method = generateJsrMerge2(ClassPool.getDefault()); + Analyzer analyzer = new Analyzer(); + analyzer.analyze(method.getDeclaringClass(), method.getMethodInfo2()); + verifyReturn(method, "java.lang.String"); + } + + private CtMethod generateDeadCode(ClassPool pool) throws Exception { + CtClass clazz = pool.makeClass(getClass().getName() + "$Generated0"); + CtClass stringClass = pool.get("java.lang.String"); + CtMethod method = new CtMethod(stringClass, "foo", new CtClass[0], clazz); + MethodInfo info = method.getMethodInfo2(); + info.setAccessFlags(AccessFlag.PUBLIC | AccessFlag.STATIC); + Bytecode code = new Bytecode(info.getConstPool(), 1, 2); + /* 0 */ code.addIconst(1); + /* 1 */ addJump(code, Opcode.GOTO, 5); + /* 4 */ code.addIconst(0); // DEAD + /* 5 */ code.addIconst(1); + /* 6 */ code.addInvokestatic(stringClass, "valueOf", stringClass, new CtClass[]{CtClass.intType}); + /* 9 */ code.addOpcode(Opcode.ARETURN); + info.setCodeAttribute(code.toCodeAttribute()); + clazz.addMethod(method); + + return method; + } + + private CtMethod generateInvalidCode(ClassPool pool) throws Exception { + CtClass clazz = pool.makeClass(getClass().getName() + "$Generated4"); + CtClass intClass = pool.get("java.lang.Integer"); + CtClass stringClass = pool.get("java.lang.String"); + CtMethod method = new CtMethod(stringClass, "foo", new CtClass[0], clazz); + MethodInfo info = method.getMethodInfo2(); + info.setAccessFlags(AccessFlag.PUBLIC | AccessFlag.STATIC); + Bytecode code = new Bytecode(info.getConstPool(), 1, 2); + /* 0 */ code.addIconst(1); + /* 1 */ code.addInvokestatic(intClass, "valueOf", intClass, new CtClass[]{CtClass.intType}); + /* 4 */ code.addOpcode(Opcode.ARETURN); + info.setCodeAttribute(code.toCodeAttribute()); + clazz.addMethod(method); + + return method; + } + + + private CtMethod generateCodeFalloff(ClassPool pool) throws Exception { + CtClass clazz = pool.makeClass(getClass().getName() + "$Generated3"); + CtClass stringClass = pool.get("java.lang.String"); + CtMethod method = new CtMethod(stringClass, "foo", new CtClass[0], clazz); + MethodInfo info = method.getMethodInfo2(); + info.setAccessFlags(AccessFlag.PUBLIC | AccessFlag.STATIC); + Bytecode code = new Bytecode(info.getConstPool(), 1, 2); + /* 0 */ code.addIconst(1); + /* 1 */ code.addInvokestatic(stringClass, "valueOf", stringClass, new CtClass[]{CtClass.intType}); + info.setCodeAttribute(code.toCodeAttribute()); + clazz.addMethod(method); + + return method; + } + + private CtMethod generateJsrMerge(ClassPool pool) throws Exception { + CtClass clazz = pool.makeClass(getClass().getName() + "$Generated1"); + CtClass stringClass = pool.get("java.lang.String"); + CtMethod method = new CtMethod(stringClass, "foo", new CtClass[0], clazz); + MethodInfo info = method.getMethodInfo2(); + info.setAccessFlags(AccessFlag.PUBLIC | AccessFlag.STATIC); + Bytecode code = new Bytecode(info.getConstPool(), 1, 2); + /* 0 */ code.addIconst(5); + /* 1 */ code.addIstore(0); + /* 2 */ addJump(code, Opcode.JSR, 7); + /* 5 */ code.addAload(0); + /* 6 */ code.addOpcode(Opcode.ARETURN); + /* 7 */ code.addAstore(1); + /* 8 */ code.addIconst(3); + /* 9 */ code.addInvokestatic(stringClass, "valueOf", stringClass, new CtClass[]{CtClass.intType}); + /* 12 */ code.addAstore(0); + /* 12 */ code.addRet(1); + info.setCodeAttribute(code.toCodeAttribute()); + clazz.addMethod(method); + //System.out.println(clazz.toClass().getMethod("foo", new Class[0]).invoke(null, new Object[0])); + + return method; + } + + private CtMethod generateJsrMerge2(ClassPool pool) throws Exception { + CtClass clazz = pool.makeClass(getClass().getName() + "$Generated2"); + CtClass stringClass = pool.get("java.lang.String"); + CtMethod method = new CtMethod(stringClass, "foo", new CtClass[0], clazz); + MethodInfo info = method.getMethodInfo2(); + info.setAccessFlags(AccessFlag.PUBLIC | AccessFlag.STATIC); + Bytecode code = new Bytecode(info.getConstPool(), 1, 2); + /* 0 */ addJump(code, Opcode.JSR, 5); + /* 3 */ code.addAload(0); + /* 4 */ code.addOpcode(Opcode.ARETURN); + /* 5 */ code.addAstore(1); + /* 6 */ code.addIconst(4); + /* 7 */ code.addInvokestatic(stringClass, "valueOf", stringClass, new CtClass[]{CtClass.intType}); + /* 10 */ code.addAstore(0); + /* 11 */ code.addRet(1); + info.setCodeAttribute(code.toCodeAttribute()); + clazz.addMethod(method); + + return method; + } + + public static class Dummy { + public Serializable commonSuperArray(int x) { + Number[] n; + + if (x > 5) { + n = new Long[10]; + } else { + n = new Double[5]; + } + + return n[x]; + } + + public Serializable commonInterfaceArray(int x) { + Serializable[] n; + + if (x > 5) { + n = new Long[10]; + } else if (x > 3) { + n = new Double[5]; + } else { + n = new String[3]; + } + + return n[x]; + } + + + public static class A {}; + public static class B1 extends A implements Serializable {}; + public static class B2 extends A implements Serializable {}; + public static class A2 implements Serializable, Cloneable {}; + public static class A3 implements Serializable, Cloneable {}; + + public static class B3 extends A {}; + public static class C31 extends B3 implements Serializable {}; + + + public void dummy(Serializable s) {} + + public Object sharedInterfaceAndSuperClass(int x) { + Serializable s; + + if (x > 5) { + s = new B1(); + } else { + s = new B2(); + } + + dummy(s); + + return s; + } + + public A sharedSuperWithSharedInterface(int x) { + A a; + + if (x > 5) { + a = new B1(); + } else if (x > 3) { + a = new B2(); + } else { + a = new C31(); + } + + return a; + } + + + public void reusedLocalMerge() { + ArrayList list = new ArrayList(); + try { + Iterator i = list.iterator(); + i.hasNext(); + } catch (Exception e) { + } + } + + public Object sharedOffsetInterfaceAndSuperClass(int x) { + Serializable s; + + if (x > 5) { + s = new B1(); + } else { + s = new C31(); + } + + dummy(s); + + return s; + } + + + public Object arrayDifferentDimensions1(int x) { + Object[] n; + + if ( x > 5) { + n = new Number[1][1]; + } else { + n = new Cloneable[1]; + } + + + return n; + } + + public Object arrayDifferentDimensions2(int x) { + Object[] n; + + if ( x> 5) { + n = new String[1][1]; + } else { + n = new Number[1][1][1][1]; + } + + return n; + } + } +} diff --git a/src/test/test/javassist/bytecode/analysis/ErrorFinder.java b/src/test/test/javassist/bytecode/analysis/ErrorFinder.java new file mode 100644 index 0000000..e131ffb --- /dev/null +++ b/src/test/test/javassist/bytecode/analysis/ErrorFinder.java @@ -0,0 +1,63 @@ +package test.javassist.bytecode.analysis; + +import java.io.BufferedReader; +import java.io.FileReader; + +import javassist.ClassPool; +import javassist.CtClass; +import javassist.CtMethod; +import javassist.bytecode.analysis.Analyzer; + +/** + * Simple testing tool that verifies class files can be analyzed. + * + * @author Jason T. Greene + */ +public class ErrorFinder { + + public static void main(String[] args) throws Exception { + ClassPool pool = ClassPool.getDefault(); + + String className = args[0]; + if (!className.equals("-file")) { + analyzeClass(pool, className); + return; + } + + FileReader reader = new FileReader(args[1]); + BufferedReader lineReader = new BufferedReader(reader); + + + String line = lineReader.readLine(); + while (line != null) { + analyzeClass(pool, line); + line = lineReader.readLine(); + } + } + + private static void analyzeClass(ClassPool pool, String className) { + try { + + CtClass clazz = pool.get(className); + CtMethod[] methods = clazz.getDeclaredMethods(); + for (int i = 0; i < methods.length; i++) + analyzeMethod(clazz, methods[i]); + } catch (Throwable e) { + System.out.println("FAIL: CLASS: " + className + " " + e.getClass() + ":" + e.getMessage()); + } + } + + private static void analyzeMethod(CtClass clazz, CtMethod method) { + String methodName = clazz.getName() + "." + method.getName() + method.getSignature(); + System.out.println("START: " + methodName); + Analyzer analyzer = new Analyzer(); + + long time = System.currentTimeMillis(); + try { + analyzer.analyze(clazz, method.getMethodInfo2()); + System.out.println("SUCCESS: " + methodName + " - " + (System.currentTimeMillis() - time)); + } catch (Exception e) { + System.out.println("FAIL: " + methodName + " - " + (e.getMessage() == null ? e.getClass().getName() : e.getMessage())); + } + } +} diff --git a/src/test/test/javassist/bytecode/analysis/ScannerTest.java b/src/test/test/javassist/bytecode/analysis/ScannerTest.java new file mode 100644 index 0000000..10f2936 --- /dev/null +++ b/src/test/test/javassist/bytecode/analysis/ScannerTest.java @@ -0,0 +1,185 @@ +package test.javassist.bytecode.analysis; + +import java.io.IOException; + +import javassist.CannotCompileException; +import javassist.ClassPool; +import javassist.CtClass; +import javassist.CtMethod; +import javassist.NotFoundException; +import javassist.bytecode.AccessFlag; +import javassist.bytecode.Bytecode; +import javassist.bytecode.MethodInfo; +import javassist.bytecode.Opcode; +import javassist.bytecode.analysis.Subroutine; +import javassist.bytecode.analysis.SubroutineScanner; +import junit.framework.TestCase; + +/** + * Tests Subroutine Scanner + * + * @author Jason T. Greene + */ +public class ScannerTest extends TestCase { + + public void testNestedFinally() throws Exception { + ClassPool pool = ClassPool.getDefault(); + generate(pool); + CtClass clazz = pool.get("test.ScannerTest$GeneratedTest"); + CtMethod method = clazz.getDeclaredMethod("doit"); + + SubroutineScanner scanner = new SubroutineScanner(); + Subroutine[] subs = scanner.scan(method.getMethodInfo2()); + + verifySubroutine(subs, 31, 31, new int[]{125, 25}); + verifySubroutine(subs, 32, 31, new int[]{125, 25}); + verifySubroutine(subs, 33, 31, new int[]{125, 25}); + verifySubroutine(subs, 60, 31, new int[]{125, 25}); + verifySubroutine(subs, 61, 31, new int[]{125, 25}); + verifySubroutine(subs, 63, 31, new int[]{125, 25}); + verifySubroutine(subs, 66, 31, new int[]{125, 25}); + verifySubroutine(subs, 69, 31, new int[]{125, 25}); + verifySubroutine(subs, 71, 31, new int[]{125, 25}); + verifySubroutine(subs, 74, 31, new int[]{125, 25}); + verifySubroutine(subs, 76, 31, new int[]{125, 25}); + verifySubroutine(subs, 77, 77, new int[]{111, 71}); + verifySubroutine(subs, 79, 77, new int[]{111, 71}); + verifySubroutine(subs, 80, 77, new int[]{111, 71}); + verifySubroutine(subs, 82, 77, new int[]{111, 71}); + verifySubroutine(subs, 85, 77, new int[]{111, 71}); + verifySubroutine(subs, 88, 77, new int[]{111, 71}); + verifySubroutine(subs, 90, 77, new int[]{111, 71}); + verifySubroutine(subs, 93, 77, new int[]{111, 71}); + verifySubroutine(subs, 95, 77, new int[]{111, 71}); + verifySubroutine(subs, 96, 96, new int[]{106, 90}); + verifySubroutine(subs, 98, 96, new int[]{106, 90}); + verifySubroutine(subs, 99, 96, new int[]{106, 90}); + verifySubroutine(subs, 101, 96, new int[]{106, 90}); + verifySubroutine(subs, 104, 96, new int[]{106, 90}); + verifySubroutine(subs, 106, 77, new int[]{111, 71}); + verifySubroutine(subs, 109, 77, new int[]{111, 71}); + verifySubroutine(subs, 111, 31, new int[]{125, 25}); + verifySubroutine(subs, 114, 31, new int[]{125, 25}); + verifySubroutine(subs, 117, 31, new int[]{125, 25}); + verifySubroutine(subs, 118, 31, new int[]{125, 25}); + verifySubroutine(subs, 120, 31, new int[]{125, 25}); + verifySubroutine(subs, 123, 31, new int[]{125, 25}); + } + + private static void verifySubroutine(Subroutine[] subs, int pos, int start, + int[] callers) { + Subroutine sub = subs[pos]; + assertNotNull(sub); + assertEquals(sub.start(), start); + for (int i = 0; i < callers.length; i++) + assertTrue(sub.callers().contains(new Integer(callers[i]))); + } + + private static void generate(ClassPool pool) throws CannotCompileException, IOException, NotFoundException { + // Generated from eclipse JDK4 compiler: + // public void doit(int x) { + // println("null"); + // try { + // println("try"); + // } catch (RuntimeException e) { + // e.printStackTrace(); + // } finally { + // switch (x) { + // default: + // case 15: + // try { + // println("inner-try"); + // } finally { + // try { + // println("inner-inner-try"); + // } finally { + // println("inner-finally"); + // } + // } + // break; + // case 1789: + // println("switch -17"); + // } + // } + //} + + CtClass clazz = pool.makeClass("test.ScannerTest$GeneratedTest"); + CtMethod method = new CtMethod(CtClass.voidType, "doit", new CtClass[] {CtClass.intType}, clazz); + MethodInfo info = method.getMethodInfo2(); + info.setAccessFlags(AccessFlag.PUBLIC); + CtClass stringClass = pool.get("java.lang.String"); + Bytecode code = new Bytecode(info.getConstPool(), 2, 9); + /* 0 */ code.addAload(0); + /* 1 */ code.addLdc("start"); + /* 3 */ code.addInvokevirtual(clazz, "println", CtClass.voidType, new CtClass[] {stringClass}); + /* 6 */ code.addAload(0); + /* 7 */ code.addLdc("try"); + /* 9 */ code.addInvokevirtual(clazz, "println", CtClass.voidType, new CtClass[] {stringClass}); + /* 12 */ addJump(code, Opcode.GOTO, 125); + /* 14 */ code.addAstore(2); + /* 16 */ code.addAload(2); + /* 17 */ code.addInvokevirtual("java.lang.Exception", "printStackTrace", "()V"); + /* 20 */ addJump(code, Opcode.GOTO, 125); + /* 23 */ code.addAstore(4); + /* 25 */ addJump(code, Opcode.JSR, 31); + /* 28 */ code.addAload(4); + /* 30 */ code.addOpcode(Opcode.ATHROW); + /* 31 */ code.addAstore(3); + /* 32 */ code.addIload(1); + int spos = code.currentPc(); + /* 33 */ code.addOpcode(Opcode.LOOKUPSWITCH); + code.addIndex(0); // 2 bytes pad - gets us to 36 + code.add32bit(60 - spos); // default + code.add32bit(2); // 2 pairs + code.add32bit(15); code.add32bit(60 - spos); + code.add32bit(1789); code.add32bit(117 - spos); + /* 60 */ code.addAload(0); + /* 61 */ code.addLdc("inner-try"); + /* 63 */ code.addInvokevirtual(clazz, "println", CtClass.voidType, new CtClass[] {stringClass}); + /* 66 */ addJump(code, Opcode.GOTO, 111); + /* 69 */ code.addAstore(6); + /* 71 */ addJump(code, Opcode.JSR, 77); + /* 74 */ code.addAload(6); + /* 76 */ code.add(Opcode.ATHROW); + /* 77 */ code.addAstore(5); + /* 79 */ code.addAload(0); + /* 80 */ code.addLdc("inner-inner-try"); + /* 82 */ code.addInvokevirtual(clazz, "println", CtClass.voidType, new CtClass[] {stringClass}); + /* 85 */ addJump(code, Opcode.GOTO, 106); + /* 88 */ code.addAstore(8); + /* 90 */ addJump(code, Opcode.JSR, 96); + /* 93 */ code.addAload(8); + /* 95 */ code.add(Opcode.ATHROW); + /* 96 */ code.addAstore(7); + /* 98 */ code.addAload(0); + /* 99 */ code.addLdc("inner-finally"); + /* 101 */ code.addInvokevirtual(clazz, "println", CtClass.voidType, new CtClass[] {stringClass}); + /* 104 */ code.addRet(7); + /* 106 */ addJump(code, Opcode.JSR, 96); + /* 109 */ code.addRet(5); + /* 111 */ addJump(code, Opcode.JSR, 77); + /* 114 */ addJump(code, Opcode.GOTO, 123); + /* 117 */ code.addAload(0); + /* 118 */ code.addLdc("switch - 1789"); + /* 120 */ code.addInvokevirtual(clazz, "println", CtClass.voidType, new CtClass[] {stringClass}); + /* 123 */ code.addRet(3); + /* 125 */ addJump(code, Opcode.JSR, 31); + /* 128 */ code.addOpcode(Opcode.RETURN); + code.addExceptionHandler(6, 12, 15, "java.lang.RuntimeException"); + code.addExceptionHandler(6, 20, 23, 0); + code.addExceptionHandler(125, 128, 23, 0); + code.addExceptionHandler(60, 69, 69, 0); + code.addExceptionHandler(111, 114, 69, 0); + code.addExceptionHandler(79, 88, 88, 0); + code.addExceptionHandler(106, 109, 88, 0); + info.setCodeAttribute(code.toCodeAttribute()); + clazz.addMethod(method); + clazz.writeFile("/tmp"); + } + + private static void addJump(Bytecode code, int opcode, int pos) { + int current = code.currentPc(); + code.addOpcode(opcode); + code.addIndex(pos - current); + } +} diff --git a/src/test/test/javassist/convert/ArrayAccessReplaceTest.java b/src/test/test/javassist/convert/ArrayAccessReplaceTest.java new file mode 100644 index 0000000..09387ce --- /dev/null +++ b/src/test/test/javassist/convert/ArrayAccessReplaceTest.java @@ -0,0 +1,433 @@ +package test.javassist.convert; + +import java.net.URL; +import java.net.URLClassLoader; +import java.util.HashMap; +import java.util.Map; + +import javassist.ClassPool; +import javassist.CodeConverter; +import javassist.CtClass; +import junit.framework.TestCase; + +public class ArrayAccessReplaceTest extends TestCase { + private static SimpleInterface simple; + + public void setUp() throws Exception { + ClassPool pool = new ClassPool(true); + CtClass echoClass = pool.get(ArrayAccessReplaceTest.class.getName() + "$Echo"); + CtClass simpleClass = pool.get(ArrayAccessReplaceTest.class.getName() + "$Simple"); + CodeConverter converter = new CodeConverter(); + converter.replaceArrayAccess(echoClass, new CodeConverter.DefaultArrayAccessReplacementMethodNames()); + simpleClass.instrument(converter); + //simpleClass.writeFile("/tmp"); + simple = (SimpleInterface) simpleClass.toClass(new URLClassLoader(new URL[0], getClass().getClassLoader()), Class.class.getProtectionDomain()).newInstance(); + } + + public void testComplex() throws Exception { + ClassPool pool = new ClassPool(true); + CtClass clazz = pool.get(ArrayAccessReplaceTest.class.getName() + "$Complex"); + + CodeConverter converter = new CodeConverter(); + converter.replaceArrayAccess(clazz, new CodeConverter.DefaultArrayAccessReplacementMethodNames()); + clazz.instrument(converter); + ComplexInterface instance = (ComplexInterface) clazz.toClass(new URLClassLoader(new URL[0], getClass().getClassLoader()), Class.class.getProtectionDomain()).newInstance(); + assertEquals(new Integer(5), instance.complexRead(4)); + } + + public void testBoolean() throws Exception { + for (int i = 0; i < 100; i++) { + boolean value = i % 5 == 0; + simple.setBoolean(i, value); + } + + for (int i = 0; i < 100; i++) { + boolean value = i % 5 == 0; + assertEquals(value, simple.getBoolean(i)); + } + } + + public void testByte() throws Exception { + for (byte i = 0; i < 100; i++) { + simple.setByte(i, i); + } + + for (byte i = 0; i < 100; i++) { + assertEquals(i, simple.getByte(i)); + } + } + + public void testShort() throws Exception { + for (short i = 0; i < 100; i++) { + simple.setShort(i, i); + } + + for (short i = 0; i < 100; i++) { + assertEquals(i, simple.getShort(i)); + } + } + + public void testChar() throws Exception { + for (char i = 0; i < 100; i++) { + simple.setChar(i, i); + } + + for (char i = 0; i < 100; i++) { + assertEquals(i, simple.getChar(i)); + } + } + + public void testInt() throws Exception { + for (int i = 0; i < 100; i++) { + simple.setInt(i, i); + } + + for (int i = 0; i < 100; i++) { + assertEquals(i, simple.getInt(i)); + } + } + + public void testLong() throws Exception { + for (int i = 0; i < 100; i++) { + simple.setLong(i, i); + } + + for (int i = 0; i < 100; i++) { + assertEquals(i, simple.getLong(i)); + } + } + + public void testFloat() throws Exception { + for (int i = 0; i < 100; i++) { + simple.setFloat(i, i); + } + + for (int i = 0; i < 100; i++) { + assertEquals((float)i, simple.getFloat(i), 0); + } + } + + public void testDouble() throws Exception { + for (int i = 0; i < 100; i++) { + simple.setDouble(i, i); + } + + for (int i = 0; i < 100; i++) { + assertEquals((double)i, simple.getDouble(i), 0); + } + } + + public void testObject() throws Exception { + for (int i = 0; i < 100; i++) { + simple.setObject(i, new Integer(i)); + } + + for (int i = 0; i < 100; i++) { + assertEquals(new Integer(i), simple.getObject(i)); + } + } + + public void testFoo() throws Exception { + for (int i = 0; i < 100; i++) { + simple.setFoo(i, new Foo(i)); + } + + for (int i = 0; i < 100; i++) { + assertEquals(new Foo(i), simple.getFoo(i)); + } + } + + public void testMulti() throws Exception { + for (int i = 2; i < 100; i++) { + simple.setMultiFoo(0, 1, i, new Foo(i)); + } + + for (int i = 2; i < 100; i++) { + assertEquals(new Foo(i), simple.getMultiFoo(0, 1, i)); + } + } + + public static class Echo { + public static Map byteMap = new HashMap(); + public static Map charMap = new HashMap(); + public static Map doubleMap = new HashMap(); + public static Map floatMap = new HashMap(); + public static Map intMap = new HashMap(); + public static Map longMap = new HashMap(); + public static Map objectMap = new HashMap(); + public static Map shortMap = new HashMap(); + + public static Object arrayReadObject(Object array, int index) { + return objectMap.get(new Integer(index)); + } + + public static void arrayWriteObject(Object array, int index, Object element) { + objectMap.put(new Integer(index), element); + } + + public static byte arrayReadByteOrBoolean(Object array, int index) { + return ((Byte)byteMap.get(new Integer(index))).byteValue(); + } + + public static void arrayWriteByteOrBoolean(Object array, int index, byte element) { + byteMap.put(new Integer(index), new Byte(element)); + } + + public static char arrayReadChar(Object array, int index) { + return ((Character)charMap.get(new Integer(index))).charValue(); + } + + public static void arrayWriteChar(Object array, int index, char element) { + charMap.put(new Integer(index), new Character(element)); + } + + public static double arrayReadDouble(Object array, int index) { + return ((Double)doubleMap.get(new Integer(index))).doubleValue(); + } + + public static void arrayWriteDouble(Object array, int index, double element) { + doubleMap.put(new Integer(index), new Double(element)); + } + + public static float arrayReadFloat(Object array, int index) { + return ((Float)floatMap.get(new Integer(index))).floatValue(); + } + + public static void arrayWriteFloat(Object array, int index, float element) { + floatMap.put(new Integer(index), new Float(element)); + } + + public static int arrayReadInt(Object array, int index) { + return ((Integer)intMap.get(new Integer(index))).intValue(); + } + + public static void arrayWriteInt(Object array, int index, int element) { + intMap.put(new Integer(index), new Integer(element)); + } + + public static long arrayReadLong(Object array, int index) { + return ((Long)longMap.get(new Integer(index))).longValue(); + } + + public static void arrayWriteLong(Object array, int index, long element) { + longMap.put(new Integer(index), new Long(element)); + } + + public static short arrayReadShort(Object array, int index) { + return ((Short)shortMap.get(new Integer(index))).shortValue(); + } + + public static void arrayWriteShort(Object array, int index, short element) { + shortMap.put(new Integer(index), new Short(element)); + } + } + + public static class Foo { + public int bar; + + public Foo(int bar) { + this.bar = bar; + } + + public int hashCode() { + return bar; + } + + public boolean equals(Object o) { + if (! (o instanceof Foo)) + return false; + + return ((Foo)o).bar == bar; + } + } + + public static interface SimpleInterface { + public void setBoolean(int pos, boolean value); + public boolean getBoolean(int pos); + + public void setByte(int pos, byte value); + public byte getByte(int pos); + + public void setShort(int pos, short value); + public short getShort(int pos); + + public void setChar(int pos, char value); + public char getChar(int pos); + + public void setInt(int pos, int value); + public int getInt(int pos); + + public void setLong(int pos, long value); + public long getLong(int pos); + + public void setFloat(int pos, float value); + public float getFloat(int pos); + + public void setDouble(int pos, double value); + public double getDouble(int pos); + + public void setObject(int pos, Object value); + public Object getObject(int pos); + + public void setFoo(int pos, Foo value); + public Foo getFoo(int pos); + + public void setMultiFoo(int one, int two, int three, Foo foo); + public Foo getMultiFoo(int one, int two, int three); + } + + public static class Simple implements SimpleInterface { + private boolean[] booleans; + private byte[] bytes; + private short[] shorts; + private char[] chars; + private int[] ints; + private long[] longs; + private float[] floats; + private double[] doubles; + private Object[] objects; + private Foo[] foos; + private Foo[][][] multi; + + public Simple() { + multi[0] = new Foo[0][0]; + multi[0][1] = new Foo[0]; + } + + public boolean getBoolean(int pos) { + return booleans[pos]; + } + + public byte getByte(int pos) { + return bytes[pos]; + } + + public char getChar(int pos) { + return chars[pos]; + } + + public double getDouble(int pos) { + return doubles[pos]; + } + + public float getFloat(int pos) { + return floats[pos]; + } + + public Foo getFoo(int pos) { + return foos[pos]; + } + + public int getInt(int pos) { + return ints[pos]; + } + + public long getLong(int pos) { + return longs[pos]; + } + + public Object getObject(int pos) { + return objects[pos]; + } + + public short getShort(int pos) { + return shorts[pos]; + } + + public Foo getMultiFoo(int one, int two, int three) { + return multi[one][two][three]; + } + + public void setBoolean(int pos, boolean value) { + booleans[pos] = value; + } + + public void setByte(int pos, byte value) { + bytes[pos] = value; + } + + public void setChar(int pos, char value) { + chars[pos] = value; + } + + public void setDouble(int pos, double value) { + doubles[pos] = value; + } + + public void setFloat(int pos, float value) { + floats[pos] = value; + } + + public void setFoo(int pos, Foo value) { + foos[pos] = value; + } + + public void setInt(int pos, int value) { + ints[pos] = value; + } + + public void setLong(int pos, long value) { + longs[pos] = value; + } + + public void setObject(int pos, Object value) { + objects[pos] = value; + } + + public void setShort(int pos, short value) { + shorts[pos] = value; + } + + public void setMultiFoo(int one, int two, int three, Foo foo) { + multi[one][two][three] = foo; + } + } + + public static interface ComplexInterface { + public Number complexRead(int x); + } + + public static class Complex implements ComplexInterface { + private Integer[] nums; + private Long[] longNums; + private static Integer justRead; + + public static Object arrayReadObject(Object array, int offset) { + return new Integer(justRead.intValue() + offset); + } + + public static void arrayWriteObject(Object array, int offset, Object element) { + justRead = (Integer) element; + } + + public Object getInteger(int i) { + return (Object) new Integer(i); + } + + public Number complexRead(int x) { + Number[] ns = null; + Number n1, n2, n3, n4; + try { + ((Object[])ns)[1] = getInteger(x); + // We have to throw an error since we can't intercept + // a guaranteed null array read yet (likely never will be able to) + throw new Error("hi"); + } catch (Error error) { + ns = nums; + } catch (Exception exception) { + ns = longNums; + } finally { + n1 = ns[1]; + n2 = ns[2]; + n3 = ns[3]; + n4 = ns[4]; + + n2.intValue(); + n3.intValue(); + n4.intValue(); + } + + return n1; + } + } +}
\ No newline at end of file diff --git a/src/test/test/javassist/proxy/JASSIST113RegressionTest.java b/src/test/test/javassist/proxy/JASSIST113RegressionTest.java new file mode 100644 index 0000000..baecdb9 --- /dev/null +++ b/src/test/test/javassist/proxy/JASSIST113RegressionTest.java @@ -0,0 +1,22 @@ +package test.javassist.proxy; + +import javassist.util.proxy.ProxyFactory; +import junit.framework.TestCase; + +/** + * Test for regression error detailed in JASSIST-113 + */ +public class JASSIST113RegressionTest extends TestCase +{ + interface Bear + { + void hibernate(); + } + + public void testProxyFactoryWithNonPublicInterface() + { + ProxyFactory proxyFactory = new ProxyFactory(); + proxyFactory.setInterfaces(new Class[]{Bear.class}); + proxyFactory.createClass(); + } +} diff --git a/src/test/test/javassist/proxy/ProxyCacheGCTest.java b/src/test/test/javassist/proxy/ProxyCacheGCTest.java new file mode 100644 index 0000000..379fefc --- /dev/null +++ b/src/test/test/javassist/proxy/ProxyCacheGCTest.java @@ -0,0 +1,121 @@ +package test.javassist.proxy; + +import javassist.*; +import javassist.util.proxy.MethodFilter; +import javassist.util.proxy.MethodHandler; +import javassist.util.proxy.ProxyFactory; +import javassist.util.proxy.ProxyObject; +import junit.framework.TestCase; + +/** + * test which checks that proxy classes are not retained after their classloader is released. + * this is a before and after test which validates JASSIST-104 + */ +public class ProxyCacheGCTest extends TestCase +{ + /** + * creates a large number of proxies in separate classloaders then lets go of the classloaders and + * forces a GC. If we run out of heap then we know there is a problem. + */ + + public final static int REPETITION_COUNT = 10000; + private ClassPool basePool; + private CtClass baseHandler; + private CtClass baseFilter; + + protected void setUp() + { + basePool = ClassPool.getDefault(); + try { + baseHandler = basePool.get("javassist.util.proxy.MethodHandler"); + baseFilter = basePool.get("javassist.util.proxy.MethodFilter"); + } catch (NotFoundException e) { + e.printStackTrace(); + fail("could not find class " + e); + } + } + + public void testCacheGC() + { + ClassLoader oldCL = Thread.currentThread().getContextClassLoader(); + try { + ProxyFactory.useCache = false; + for (int i = 0; i < REPETITION_COUNT; i++) { + ClassLoader newCL = new TestLoader(); + try { + Thread.currentThread().setContextClassLoader(newCL); + createProxy(i); + } finally { + Thread.currentThread().setContextClassLoader(oldCL); + } + } + } finally { + ProxyFactory.useCache = true; + } + } + + /** + * called when a specific classloader is in place on the thread to create a method handler, method filter + * and proxy in the current loader and then + */ + public void createProxy(int counter) + { + try { + ClassPool classPool = new ClassPool(basePool); + + // create a target class in the current class loader + String targetName = "test.javassist.MyTarget_" + counter; + String targetConstructorName = "MyTarget_" + counter; + CtClass ctTargetClass = classPool.makeClass(targetName); + CtMethod targetMethod = CtNewMethod.make("public Object test() { return this; }", ctTargetClass); + ctTargetClass.addMethod(targetMethod); + CtConstructor targetConstructor = CtNewConstructor.make("public " + targetConstructorName + "() { }", ctTargetClass); + ctTargetClass.addConstructor(targetConstructor); + + // create a handler in the current classloader + String handlerName = "test.javassist.MyHandler_" + counter; + CtClass ctHandlerClass = classPool.makeClass(handlerName); + ctHandlerClass.addInterface(baseHandler); + CtMethod handlerInvoke = CtNewMethod.make("public Object invoke(Object self, java.lang.reflect.Method thisMethod, java.lang.reflect.Method proceed, Object[] args) throws Throwable { return proceed.invoke(self, args); }", ctHandlerClass); + ctHandlerClass.addMethod(handlerInvoke); + + // create a filter in the current classloader + String filterName = "test.javassist.MyFilter" + counter; + CtClass ctFilterClass = classPool.makeClass(filterName); + ctFilterClass.addInterface(baseFilter); + CtMethod filterIsHandled = CtNewMethod.make("public boolean isHandled(java.lang.reflect.Method m) { return true; }", ctFilterClass); + ctFilterClass.addMethod(filterIsHandled); + + // now create a proxyfactory and use it to create a proxy + + ProxyFactory factory = new ProxyFactory(); + Class javaTargetClass = classPool.toClass(ctTargetClass); + Class javaHandlerClass = classPool.toClass(ctHandlerClass); + Class javaFilterClass = classPool.toClass(ctFilterClass); + + MethodHandler handler= (MethodHandler)javaHandlerClass.newInstance(); + MethodFilter filter = (MethodFilter)javaFilterClass.newInstance(); + + // ok, now create a factory and a proxy class and proxy from that factory + factory.setFilter(filter); + factory.setSuperclass(javaTargetClass); + // factory.setSuperclass(Object.class); + + Class proxyClass = factory.createClass(); + Object target = proxyClass.newInstance(); + ((ProxyObject)target).setHandler(handler); + } catch (Exception e) { + e.printStackTrace(); + fail("cannot create proxy " + e); + } + + } + + /** + * a classloader which inherits from the system class loader and within which a proxy handler, + * filter and proxy will be located. + */ + public static class TestLoader extends ClassLoader + { + } +} diff --git a/src/test/test/javassist/proxy/ProxyFactoryCompatibilityTest.java b/src/test/test/javassist/proxy/ProxyFactoryCompatibilityTest.java new file mode 100644 index 0000000..5b72d82 --- /dev/null +++ b/src/test/test/javassist/proxy/ProxyFactoryCompatibilityTest.java @@ -0,0 +1,115 @@ +package test.javassist.proxy; + +import javassist.*; +import javassist.util.proxy.MethodFilter; +import javassist.util.proxy.MethodHandler; +import javassist.util.proxy.ProxyFactory; +import javassist.util.proxy.ProxyObject; +import junit.framework.TestCase; + +import java.lang.reflect.Method; + +/** + * test which checks that it is still possible to use the old style proxy factory api + * to create proxy classes which set their own handler. it checks that caching is + * automatically disabled if this legacy api is used. it also exercises the new style + * api, ensuring that caching works correctly with this model. + */ +public class ProxyFactoryCompatibilityTest extends TestCase +{ + private ClassPool basePool; + MethodFilter filter; + MethodHandler handler; + + protected void setUp() + { + basePool = ClassPool.getDefault(); + filter = new MethodFilter() { + public boolean isHandled(Method m) { + return !m.getName().equals("finalize"); + } + }; + + handler = new MethodHandler() { + public Object invoke(Object self, Method m, Method proceed, + Object[] args) throws Throwable { + System.out.println("calling: " + m.getName()); + return proceed.invoke(self, args); // execute the original method. + } + }; + } + + public void testFactoryCompatibility() throws Exception + { + System.out.println("ProxyFactory.useCache = " + ProxyFactory.useCache); + // create a factory which, by default, uses caching + ProxyFactory factory = new ProxyFactory(); + factory.setSuperclass(TestClass.class); + factory.setInterfaces(new Class[] { TestInterface.class}); + factory.setFilter(filter); + + // create the same class twice and check that it is reused + Class proxyClass1 = factory.createClass(); + System.out.println("created first class " + proxyClass1.getName()); + TestClass proxy1 = (TestClass)proxyClass1.newInstance(); + ((ProxyObject) proxy1).setHandler(handler); + proxy1.testMethod(); + assertTrue(proxy1.isTestCalled()); + + Class proxyClass2 = factory.createClass(); + System.out.println("created second class " + proxyClass2.getName()); + TestClass proxy2 = (TestClass)proxyClass2.newInstance(); + ((ProxyObject) proxy2).setHandler(handler); + proxy2.testMethod(); + assertTrue(proxy2.isTestCalled()); + + assertTrue(proxyClass1 == proxyClass2); + + // create a factory which, by default, uses caching then set the handler so it creates + // classes which do not get cached. + ProxyFactory factory2 = new ProxyFactory(); + factory.setSuperclass(TestClass.class); + factory.setInterfaces(new Class[] { TestInterface.class}); + factory.setFilter(filter); + factory.setHandler(handler); + + // create the same class twice and check that it is reused + Class proxyClass3 = factory.createClass(); + System.out.println("created third class " + proxyClass3.getName()); + TestClass proxy3 = (TestClass)proxyClass3.newInstance(); + proxy3.testMethod(); + assertTrue(proxy3.isTestCalled()); + + Class proxyClass4 = factory.createClass(); + System.out.println("created fourth class " + proxyClass4.getName()); + TestClass proxy4 = (TestClass)proxyClass4.newInstance(); + proxy4.testMethod(); + assertTrue(proxy4.isTestCalled()); + + assertTrue(proxyClass3 != proxyClass4); + } + + /** + * test class used as the super for the proxy + */ + public static class TestClass { + private boolean testCalled = false; + public void testMethod() + { + // record the call + testCalled = true; + } + public boolean isTestCalled() + { + return testCalled; + } + } + + /** + * test interface used as an interface implemented by the proxy + */ + public static interface TestInterface { + public void testMethod(); + } + +}
\ No newline at end of file diff --git a/src/test/test/javassist/proxy/ProxySerializationTest.java b/src/test/test/javassist/proxy/ProxySerializationTest.java new file mode 100644 index 0000000..28125de --- /dev/null +++ b/src/test/test/javassist/proxy/ProxySerializationTest.java @@ -0,0 +1,151 @@ +package test.javassist.proxy; + +import javassist.util.proxy.*; +import junit.framework.TestCase; + +import java.io.*; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +/** + * Test to ensure that serialization and deserialization of javassist proxies via + * {@link javassist.util.proxy.ProxyObjectOutputStream} and @link javassist.util.proxy.ProxyObjectInputStream} + * reuses classes located in the proxy cache. This tests the fixes provided for JASSIST-42 and JASSIST-97. + */ +public class ProxySerializationTest extends TestCase +{ + public void testSerialization() + { + ProxyFactory factory = new ProxyFactory(); + factory.setSuperclass(TestClass.class); + factory.setInterfaces(new Class[] {TestInterface.class}); + + factory.setUseWriteReplace(true); + Class proxyClass = factory.createClass(new TestFilter()); + + MethodHandler handler = new TestHandler(); + + // first try serialization using writeReplace + + try { + String name = "proxytest_1"; + Constructor constructor = proxyClass.getConstructor(new Class[] {String.class}); + TestClass proxy = (TestClass)constructor.newInstance(new Object[] {name}); + ((ProxyObject)proxy).setHandler(handler); + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + ObjectOutputStream out = new ObjectOutputStream(bos); + out.writeObject(proxy); + out.close(); + byte[] bytes = bos.toByteArray(); + ByteArrayInputStream bis = new ByteArrayInputStream(bytes); + ObjectInputStream in = new ObjectInputStream(bis); + TestClass newProxy = (TestClass)in.readObject(); + // inherited fields should not have been deserialized + assertTrue("new name should be null", newProxy.getName() == null); + // since we are reading into the same JVM the new proxy should have the same class as the old proxy + assertTrue("classes should be equal", newProxy.getClass() == proxy.getClass()); + } catch (Exception e) { + e.printStackTrace(); + fail(); + } + + // second try serialization using proxy object output/input streams + + factory.setUseWriteReplace(false); + proxyClass = factory.createClass(new TestFilter()); + + try { + String name = "proxytest_2"; + Constructor constructor = proxyClass.getConstructor(new Class[] {String.class}); + TestClass proxy = (TestClass)constructor.newInstance(new Object[] {name}); + ((ProxyObject)proxy).setHandler(handler); + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + ProxyObjectOutputStream out = new ProxyObjectOutputStream(bos); + out.writeObject(proxy); + out.close(); + byte[] bytes = bos.toByteArray(); + ByteArrayInputStream bis = new ByteArrayInputStream(bytes); + ProxyObjectInputStream in = new ProxyObjectInputStream(bis); + TestClass newProxy = (TestClass)in.readObject(); + // inherited fields should have been deserialized + assertTrue("names should be equal", proxy.getName().equals(newProxy.getName())); + // since we are reading into the same JVM the new proxy should have the same class as the old proxy + assertTrue("classes should still be equal", newProxy.getClass() == proxy.getClass()); + } catch (Exception e) { + e.printStackTrace(); + fail(); + } + } + + public static class TestFilter implements MethodFilter, Serializable + { + public boolean isHandled(Method m) { + if (m.getName().equals("getName")) { + return true; + } + return false; + } + + public boolean equals(Object o) + { + if (o instanceof TestFilter) { + // all test filters are equal + return true; + } + + return false; + } + + public int hashCode() + { + return TestFilter.class.hashCode(); + } + } + + public static class TestHandler implements MethodHandler, Serializable + { + public Object invoke(Object self, Method thisMethod, Method proceed, Object[] args) throws Throwable + { + return proceed.invoke(self, args); + } + public boolean equals(Object o) + { + if (o instanceof TestHandler) { + // all test handlers are equal + return true; + } + + return false; + } + + public int hashCode() + { + return TestHandler.class.hashCode(); + } + } + + public static class TestClass implements Serializable + { + public String name; + + public TestClass() + { + } + + public TestClass(String name) + { + this.name = name; + } + + public String getName() + { + return name; + } + } + + public static interface TestInterface + { + public String getName(); + } +} diff --git a/src/test/test/javassist/proxy/ProxySimpleTest.java b/src/test/test/javassist/proxy/ProxySimpleTest.java new file mode 100644 index 0000000..f74fce4 --- /dev/null +++ b/src/test/test/javassist/proxy/ProxySimpleTest.java @@ -0,0 +1,64 @@ +package test.javassist.proxy; + +import junit.framework.TestCase; + +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.lang.reflect.Method; + +import javassist.util.proxy.ProxyFactory; + +public class ProxySimpleTest extends TestCase { + public void testReadWrite() throws Exception { + final String fileName = "read-write.bin"; + ProxyFactory.ClassLoaderProvider cp = ProxyFactory.classLoaderProvider; + ProxyFactory.classLoaderProvider = new ProxyFactory.ClassLoaderProvider() { + public ClassLoader get(ProxyFactory pf) { + return new javassist.Loader(); + } + }; + ProxyFactory pf = new ProxyFactory(); + pf.setSuperclass(ReadWriteData.class); + Object data = pf.createClass().newInstance(); + // Object data = new ReadWriteData(); + ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(fileName)); + oos.writeObject(data); + oos.close(); + ProxyFactory.classLoaderProvider = cp; + + ObjectInputStream ois = new ObjectInputStream(new FileInputStream(fileName)); + Object data2 = ois.readObject(); + ois.close(); + int i = ((ReadWriteData)data2).foo(); + assertEquals(4, i); + } + + public static class ReadWriteData implements Serializable { + public int foo() { return 4; } + } + + public void testWriteReplace() throws Exception { + ProxyFactory pf = new ProxyFactory(); + pf.setSuperclass(WriteReplace.class); + Object data = pf.createClass().newInstance(); + assertEquals(data, ((WriteReplace)data).writeReplace()); + + ProxyFactory pf2 = new ProxyFactory(); + pf2.setSuperclass(WriteReplace2.class); + Object data2 = pf2.createClass().newInstance(); + Method meth = data2.getClass().getDeclaredMethod("writeReplace", new Class[0]); + assertEquals("javassist.util.proxy.SerializedProxy", + meth.invoke(data2, new Object[0]).getClass().getName()); + } + + public static class WriteReplace implements Serializable { + public Object writeReplace() { return this; } + } + + public static class WriteReplace2 implements Serializable { + public Object writeReplace(int i) { return new Integer(i); } + } +} diff --git a/tutorial/brown.css b/tutorial/brown.css new file mode 100644 index 0000000..7570549 --- /dev/null +++ b/tutorial/brown.css @@ -0,0 +1,19 @@ +h1,h2,h3 { + color:#663300; + padding:4px 6px 6px 10px; + border-width:1px 0px 1px 0px; + border-color:#F5DEB3; + border-style:solid; +} + +h3 { + padding-left: 30px; +} + +h4 { + color:#663300; +} + +em { + color:#cc0000; +} diff --git a/tutorial/tutorial.html b/tutorial/tutorial.html new file mode 100644 index 0000000..d62743d --- /dev/null +++ b/tutorial/tutorial.html @@ -0,0 +1,1101 @@ +<html> +<head> + <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> + <title>Javassist Tutorial</title> + <link rel="stylesheet" type="text/css" href="brown.css"> +</head> +<body> + +<b> +<font size="+3"> +Getting Started with Javassist +</font> + +<p><font size="+2"> +Shigeru Chiba +</font> +</b> + +<p><div align="right"><a href="tutorial2.html">Next page</a></div> + +<ul>1. <a href="#read">Reading and writing bytecode</a> +<br>2. <a href="#pool">ClassPool</a> +<br>3. <a href="#load">Class loader</a> +<br>4. <a href="tutorial2.html#intro">Introspection and customization</a> +<br>5. <a href="tutorial3.html#intro">Bytecode level API</a> +<br>6. <a href="tutorial3.html#generics">Generics</a> +<br>7. <a href="tutorial3.html#varargs">Varargs</a> +<br>8. <a href="tutorial3.html#j2me">J2ME</a> +</ul> + +<p><br> + +<a name="read"> +<h2>1. Reading and writing bytecode</h2> + +<p>Javassist is a class library for dealing with Java bytecode. +Java bytecode is stored in a binary file called a class file. +Each class file contains one Java class or interface. + +<p>The class <code>Javassist.CtClass</code> is an absatract +representation of a class file. A <code>CtClass</code> (compile-time +class) object is a handle for dealing with a class file. The +following program is a very simple example: + +<ul><pre> +ClassPool pool = ClassPool.getDefault(); +CtClass cc = pool.get("test.Rectangle"); +cc.setSuperclass(pool.get("test.Point")); +cc.writeFile(); +</pre></ul> + +<p>This program first obtains a <code>ClassPool</code> object, which +controls bytecode modification with Javassist. The +<code>ClassPool</code> object is a container of <code>CtClass</code> +object representing a class file. It reads a class file on demand for +constructing a <code>CtClass</code> object and records the +constructed object for responding later accesses. + +To modify the definition of a class, the users must first obtain +from a <code>ClassPool</code> object +a reference to a <code>CtClass</code> object representing that class. +<code>get()</code> in <code>ClassPool</code> is used for this purpose. +In the case of the program shown above, the +<code>CtClass</code> object representing a class +<code>test.Rectangle</code> is obtained from the +<code>ClassPool</code> object and it is assigned to a variable +<code>cc</code>. +The <code>ClassPool</code> object returned by <code>getDfault()</code> +searches the default system search path. + +<p>From the implementation viewpoint, <code>ClassPool</code> is a hash +table of <code>CtClass</code> objects, which uses the class names as +keys. <code>get()</code> in <code>ClassPool</code> searches this hash +table to find a <code>CtClass</code> object associated with the +specified key. If such a <code>CtClass</code> object is not found, +<code>get()</code> reads a class file to construct a new +<code>CtClass</code> object, which is recorded in the hash table and +then returned as the resulting value of <code>get()</code>. + +<p>The <code>CtClass</code> object obtained from a <code>ClassPool</code> +object can be modified +(<a href="tutorial2.html#intro">details of how to modify +a <code>CtClass</code></a> will be presented later). +In the example above, it is modified so that the superclass of +<code>test.Rectangle</code> is changed into a class +<code>test.Point</code>. This change is reflected on the original +class file when <code>writeFile()</code> in <code>CtClass()</code> is +finally called. + +<p><code>writeFile()</code> translates the <code>CtClass</code> object +into a class file and writes it on a local disk. +Javassist also provides a method for directly obtaining the +modified bytecode. To obtain the bytecode, call <code>toBytecode()</code>: + +<ul><pre> +byte[] b = cc.toBytecode(); +</pre></ul> + +<p>You can directly load the <code>CtClass</code> as well: + +<ul><pre> +Class clazz = cc.toClass(); +</pre></ul> + +<p><code>toClass()</code> requests the context class loader for the current +thread to load the class file represented by the <code>CtClass</code>. It +returns a <code>java.lang.Class</code> object representing the loaded class. +For more details, please see <a href="#toclass">this section below</a>. + +<a name="def"> +<h4>Defining a new class</h4> + +<p>To define a new class from scratch, <code>makeClass()</code> +must be called on a <code>ClassPool</code>. + +<ul><pre> +ClassPool pool = ClassPool.getDefault(); +CtClass cc = pool.makeClass("Point"); +</pre></ul> + +<p>This program defines a class <code>Point</code> +including no members. +Member methods of <code>Point</code> can be created with +factory methods declared in <code>CtNewMethod</code> and +appended to <code>Point</code> with <code>addMethod()</code> +in <code>CtClass</code>. + +<p><code>makeClass()</code> cannot create a new interface; +<code>makeInterface()</code> in <code>ClassPool</code> can do. +Member methods in an interface can be created with +<code>abstractMethod()</code> in <code>CtNewMethod</code>. +Note that an interface method is an abstract method. + +<a name="frozenclasses"> +<h4>Frozen classes</h4></a> + +<p>If a <code>CtClass</code> object is converted into a class file by +<code>writeFile()</code>, <code>toClass()</code>, or +<code>toBytecode()</code>, Javassist freezes that <code>CtClass</code> +object. Further modifications of that <code>CtClass</code> object are +not permitted. This is for warning the developers when they attempt +to modify a class file that has been already loaded since the JVM does +not allow reloading a class. + +<p>A frozen <code>CtClass</code> can be defrost so that +modifications of the class definition will be permitted. For example, + +<ul><pre> +CtClasss cc = ...; + : +cc.writeFile(); +cc.defrost(); +cc.setSuperclass(...); // OK since the class is not frozen. +</pre></ul> + +<p>After <code>defrost()</code> is called, the <code>CtClass</code> +object can be modified again. + +<p>If <code>ClassPool.doPruning</code> is set to <code>true</code>, +then Javassist prunes the data structure contained +in a <code>CtClass</code> object +when Javassist freezes that object. +To reduce memory +consumption, pruning discards unnecessary attributes +(<code>attribute_info</code> structures) in that object. +For example, <code>Code_attribute</code> structures (method bodies) +are discarded. +Thus, after a +<code>CtClass</code> object is pruned, the bytecode of a method is not +accessible except method names, signatures, and annotations. +The pruned <code>CtClass</code> object cannot be defrost again. +The default value of <code>ClassPool.doPruning</code> is <code>false</code>. + +<p>To disallow pruning a particular <code>CtClass</code>, +<code>stopPruning()</code> must be called on that object in advance: + +<ul><pre> +CtClasss cc = ...; +cc.stopPruning(true); + : +cc.writeFile(); // convert to a class file. +// cc is not pruned. +</pre></ul> + +<p>The <code>CtClass</code> object <code>cc</code> is not pruned. +Thus it can be defrost after <code>writeFile()</code> is called. + +<ul><b>Note:</b> +While debugging, you might want to temporarily stop pruning and freezing +and write a modified class file to a disk drive. +<code>debugWriteFile()</code> is a convenient method +for that purpose. It stops pruning, writes a class file, defrosts it, +and turns pruning on again (if it was initially on). +</ul> + + + +<h4>Class search path</h4> + +<p>The default <code>ClassPool</code> returned +by a static method <code>ClassPool.getDefault()</code> +searches the same path that the underlying JVM (Java virtual machine) has. +<em>If a program is running on a web application server such as JBoss and Tomcat, +the <code>ClassPool</code> object may not be able to find user classes</em> +since such a web application server uses multiple class loaders as well as +the system class loader. In that case, an additional class path must be +registered to the <code>ClassPool</code>. Suppose that <code>pool</code> +refers to a <code>ClassPool</code> object: + +<ul><pre> +pool.insertClassPath(new ClassClassPath(this.getClass())); +</pre></ul> + +<p> +This statement registers the class path that was used for loading +the class of the object that <code>this</code> refers to. +You can use any <code>Class</code> object as an argument instead of +<code>this.getClass()</code>. The class path used for loading the +class represented by that <code>Class</code> object is registered. + +<p> +You can register a directory name as the class search path. +For example, the following code adds a directory +<code>/usr/local/javalib</code> +to the search path: + +<ul><pre> +ClassPool pool = ClassPool.getDefault(); +pool.insertClassPath("/usr/local/javalib"); +</pre></ul> + +<p>The search path that the users can add is not only a directory but also +a URL: + +<ul><pre> +ClassPool pool = ClassPool.getDefault(); +ClassPath cp = new URLClassPath("www.javassist.org", 80, "/java/", "org.javassist."); +pool.insertClassPath(cp); +</pre></ul> + +<p>This program adds "http://www.javassist.org:80/java/" to the class search +path. This URL is used only for searching classes belonging to a +package <code>org.javassist</code>. For example, to load a class +<code>org.javassist.test.Main</code>, its class file will be obtained from: + +<ul><pre>http://www.javassist.org:80/java/org/javassist/test/Main.class +</pre></ul> + +<p>Furthermore, you can directly give a byte array +to a <code>ClassPool</code> object +and construct a <code>CtClass</code> object from that array. To do this, +use <code>ByteArrayClassPath</code>. For example, + +<ul><pre> +ClassPool cp = ClassPool.getDefault(); +byte[] b = <em>a byte array</em>; +String name = <em>class name</em>; +cp.insertClassPath(new ByteArrayClassPath(name, b)); +CtClass cc = cp.get(name); +</pre></ul> + +<p>The obtained <code>CtClass</code> object represents +a class defined by the class file specified by <code>b</code>. +The <code>ClassPool</code> reads a class file from the given +<code>ByteArrayClassPath</code> if <code>get()</code> is called +and the class name given to <code>get()</code> is equal to +one specified by <code>name</code>. + +<p>If you do not know the fully-qualified name of the class, then you +can use <code>makeClass()</code> in <code>ClassPool</code>: + +<ul><pre> +ClassPool cp = ClassPool.getDefault(); +InputStream ins = <em>an input stream for reading a class file</em>; +CtClass cc = cp.makeClass(ins); +</pre></ul> + +<p><code>makeClass()</code> returns the <code>CtClass</code> object +constructed from the given input stream. You can use +<code>makeClass()</code> for eagerly feeding class files to +the <code>ClassPool</code> object. This might improve performance +if the search path includes a large jar file. Since +a <code>ClassPool</code> object reads a class file on demand, +it might repeatedly search the whole jar file for every class file. +<code>makeClass()</code> can be used for optimizing this search. +The <code>CtClass</code> constructed by <code>makeClass()</code> +is kept in the <code>ClassPool</code> object and the class file is never +read again. + +<p>The users can extend the class search path. They can define a new +class implementing <code>ClassPath</code> interface and give an +instance of that class to <code>insertClassPath()</code> in +<code>ClassPool</code>. This allows a non-standard resource to be +included in the search path. + +<p><br> + +<a name="pool"> +<h2>2. ClassPool</h2> + +<p> +A <code>ClassPool</code> object is a container of <code>CtClass</code> +objects. Once a <code>CtClass</code> object is created, it is +recorded in a <code>ClassPool</code> for ever. This is because a +compiler may need to access the <code>CtClass</code> object later when +it compiles source code that refers to the class represented by that +<code>CtClass</code>. + +<p> +For example, suppose that a new method <code>getter()</code> is added +to a <code>CtClass</code> object representing <code>Point</code> +class. Later, the program attempts to compile source code including a +method call to <code>getter()</code> in <code>Point</code> and use the +compiled code as the body of a method, which will be added to another +class <code>Line</code>. If the <code>CtClass</code> object representing +<code>Point</code> is lost, the compiler cannot compile the method call +to <code>getter()</code>. Note that the original class definition does +not include <code>getter()</code>. Therefore, to correctly compile +such a method call, the <code>ClassPool</code> +must contain all the instances of <code>CtClass</code> all the time of +program execution. + +<a name="avoidmemory"> +<h4>Avoid out of memory</h4> +</a> + +<p> +This specification of <code>ClassPool</code> may cause huge memory +consumption if the number of <code>CtClass</code> objects becomes +amazingly large (this rarely happens since Javassist tries to reduce +memory consumption in <a href="#frozenclasses">various ways</a>). +To avoid this problem, you +can explicitly remove an unnecessary <code>CtClass</code> object from +the <code>ClassPool</code>. If you call <code>detach()</code> on a +<code>CtClass</code> object, then that <code>CtClass</code> object is +removed from the <code>ClassPool</code>. For example, + +<ul><pre> +CtClass cc = ... ; +cc.writeFile(); +cc.detach(); +</pre></ul> + +<p>You must not call any method on that +<code>CtClass</code> object after <code>detach()</code> is called. +However, you can call <code>get()</code> on <code>ClassPool</code> +to make a new instance of <code>CtClass</code> representing +the same class. If you call <code>get()</code>, the <code>ClassPool</code> +reads a class file again and newly creates a <code>CtClass</code> +object, which is returned by <code>get()</code>. + +<p> +Another idea is to occasionally replace a <code>ClassPool</code> with +a new one and discard the old one. If an old <code>ClassPool</code> +is garbage collected, the <code>CtClass</code> objects included in +that <code>ClassPool</code> are also garbage collected. +To create a new instance of <code>ClassPool</code>, execute the following +code snippet: + +<ul><pre> +ClassPool cp = new ClassPool(true); +// if needed, append an extra search path by appendClassPath() +</pre></ul> + +<p>This creates a <code>ClassPool</code> object that behaves as the +default <code>ClassPool</code> returned by +<code>ClassPool.getDefault()</code> does. +Note that <code>ClassPool.getDefault()</code> is a singleton factory method +provided for convenience. It creates a <code>ClassPool</code> object in +the same way shown above although it keeps a single instance of +<code>ClassPool</code> and reuses it. +A <code>ClassPool</code> object returned by <code>getDefault()</code> +does not have a special role. <code>getDefault()</code> is a convenience +method. + +<p>Note that <code>new ClassPool(true)</code> is a convenient constructor, +which constructs a <code>ClassPool</code> object and appends the system +search path to it. Calling that constructor is +equivalent to the following code: + +<ul><pre> +ClassPool cp = new ClassPool(); +cp.appendSystemPath(); // or append another path by appendClassPath() +</pre></ul> + +<h4>Cascaded ClassPools</h4> + +<p> +<em>If a program is running on a web application server,</em> +creating multiple instances of <code>ClassPool</code> might be necessary; +an instance of <code>ClassPool</code> should be created +for each class loader (i.e. container). +The program should create a <code>ClassPool</code> object by not calling +<code>getDefault()</code> but a constructor of <code>ClassPool</code>. + +<p> +Multiple <code>ClassPool</code> objects can be cascaded like +<code>java.lang.ClassLoader</code>. For example, + +<ul><pre> +ClassPool parent = ClassPool.getDefault(); +ClassPool child = new ClassPool(parent); +child.insertClassPath("./classes"); +</pre></ul> + +<p> +If <code>child.get()</code> is called, the child <code>ClassPool</code> +first delegates to the parent <code>ClassPool</code>. If the parent +<code>ClassPool</code> fails to find a class file, then the child +<code>ClassPool</code> attempts to find a class file +under the <code>./classes</code> directory. + +<p> +If <code>child.childFirstLookup</code> is true, the child +<code>ClassPool</code> attempts to find a class file before delegating +to the parent <code>ClassPool</code>. For example, + +<ul><pre> +ClassPool parent = ClassPool.getDefault(); +ClassPool child = new ClassPool(parent); +child.appendSystemPath(); // the same class path as the default one. +child.childFirstLookup = true; // changes the behavior of the child. +</pre></ul> + +<h4>Changing a class name for defining a new class</h4> + +<p>A new class can be defined as a copy of an existing class. +The program below does that: + +<ul><pre> +ClassPool pool = ClassPool.getDefault(); +CtClass cc = pool.get("Point"); +cc.setName("Pair"); +</pre></ul> + +<p>This program first obtains the <code>CtClass</code> object for +class <code>Point</code>. Then it calls <code>setName()</code> to +give a new name <code>Pair</code> to that <code>CtClass</code> object. +After this call, all occurrences of the class name in the class +definition represented by that <code>CtClass</code> object are changed +from <code>Point</code> to <code>Pair</code>. The other part of the +class definition does not change. + +<p>Note that <code>setName()</code> in <code>CtClass</code> changes a +record in the <code>ClassPool</code> object. From the implementation +viewpoint, a <code>ClassPool</code> object is a hash table of +<code>CtClass</code> objects. <code>setName()</code> changes +the key associated to the <code>CtClass</code> object in the hash +table. The key is changed from the original class name to the new +class name. + +<p>Therefore, if <code>get("Point")</code> is later called on the +<code>ClassPool</code> object again, then it never returns the +<code>CtClass</code> object that the variable <code>cc</code> refers to. +The <code>ClassPool</code> object reads +a class file +<code>Point.class</code> again and it constructs a new <code>CtClass</code> +object for class <code>Point</code>. +This is because the <code>CtClass</code> object associated with the name +<code>Point</code> does not exist any more. +See the followings: + +<ul><pre> +ClassPool pool = ClassPool.getDefault(); +CtClass cc = pool.get("Point"); +CtClass cc1 = pool.get("Point"); // cc1 is identical to cc. +cc.setName("Pair"); +CtClass cc2 = pool.get("Pair"); // cc2 is identical to cc. +CtClass cc3 = pool.get("Point"); // cc3 is not identical to cc. +</pre></ul> + +<p><code>cc1</code> and <code>cc2</code> refer to the same instance of +<code>CtClass</code> that <code>cc</code> does whereas +<code>cc3</code> does not. Note that, after +<code>cc.setName("Pair")</code> is executed, the <code>CtClass</code> +object that <code>cc</code> and <code>cc1</code> refer to represents +the <code>Pair</code> class. + +<p>The <code>ClassPool</code> object is used to maintain one-to-one +mapping between classes and <code>CtClass</code> objects. Javassist +never allows two distinct <code>CtClass</code> objects to represent +the same class unless two independent <code>ClassPool</code> are created. +This is a significant feature for consistent program +transformation. + +<p>To create another copy of the default instance of +<code>ClassPool</code>, which is returned by +<code>ClassPool.getDefault()</code>, execute the following code +snippet (this code was already <a href="#avoidmemory">shown above</a>): + +<ul><pre> +ClassPool cp = new ClassPool(true); +</pre></ul> + +<p>If you have two <code>ClassPool</code> objects, then you can +obtain, from each <code>ClassPool</code>, a distinct +<code>CtClass</code> object representing the same class file. You can +differently modify these <code>CtClass</code> objects to generate +different versions of the class. + +<h4>Renaming a frozen class for defining a new class</h4> + +<p>Once a <code>CtClass</code> object is converted into a class file +by <code>writeFile()</code> or <code>toBytecode()</code>, Javassist +rejects further modifications of that <code>CtClass</code> object. +Hence, after the <code>CtClass</code> object representing <code>Point</code> +class is converted into a class file, you cannot define <code>Pair</code> +class as a copy of <code>Point</code> since executing <code>setName()</code> +on <code>Point</code> is rejected. +The following code snippet is wrong: + +<ul><pre> +ClassPool pool = ClassPool.getDefault(); +CtClass cc = pool.get("Point"); +cc.writeFile(); +cc.setName("Pair"); // wrong since writeFile() has been called. +</pre></ul> + +<p>To avoid this restriction, you should call <code>getAndRename()</code> +in <code>ClassPool</code>. For example, + +<ul><pre> +ClassPool pool = ClassPool.getDefault(); +CtClass cc = pool.get("Point"); +cc.writeFile(); +CtClass cc2 = pool.getAndRename("Point", "Pair"); +</pre></ul> + +<p>If <code>getAndRename()</code> is called, the <code>ClassPool</code> +first reads <code>Point.class</code> for creating a new <code>CtClass</code> +object representing <code>Point</code> class. However, it renames that +<code>CtClass</code> object from <code>Point</code> to <code>Pair</code> before +it records that <code>CtClass</code> object in a hash table. +Thus <code>getAndRename()</code> +can be executed after <code>writeFile()</code> or <code>toBytecode()</code> +is called on the the <code>CtClass</code> object representing <code>Point</code> +class. + +<p><br> + +<a name="load"> +<h2>3. Class loader</h2> + +<p>If what classes must be modified is known in advance, +the easiest way for modifying the classes is as follows: + +<ul><li>1. Get a <code>CtClass</code> object by calling + <code>ClassPool.get()</code>, + <li>2. Modify it, and + <li>3. Call <code>writeFile()</code> or <code>toBytecode()</code> + on that <code>CtClass</code> object to obtain a modified class file. +</ul> + +<p>If whether a class is modified or not is determined at load time, +the users must make Javassist collaborate with a class loader. +Javassist can be used with a class loader so that bytecode can be +modified at load time. The users of Javassist can define their own +version of class loader but they can also use a class loader provided +by Javassist. + + +<p><br> + +<a name="toclass"> +<h3>3.1 The <code>toClass</code> method in <code>CtClass</code></h3> +</a> + +<p>The <code>CtClass</code> provides a convenience method +<code>toClass()</code>, which requests the context class loader for +the current thread to load the class represented by the <code>CtClass</code> +object. To call this method, the caller must have appropriate permission; +otherwise, a <code>SecurityException</code> may be thrown. + +<p>The following program shows how to use <code>toClass()</code>: + +<ul><pre> +public class Hello { + public void say() { + System.out.println("Hello"); + } +} + +public class Test { + public static void main(String[] args) throws Exception { + ClassPool cp = ClassPool.getDefault(); + CtClass cc = cp.get("Hello"); + CtMethod m = cc.getDeclaredMethod("say"); + m.insertBefore("{ System.out.println(\"Hello.say():\"); }"); + Class c = cc.toClass(); + Hello h = (Hello)c.newInstance(); + h.say(); + } +} +</pre></ul> + +<p><code>Test.main()</code> inserts a call to <code>println()</code> +in the method body of <code>say()</code> in <code>Hello</code>. Then +it constructs an instance of the modified <code>Hello</code> class +and calls <code>say()</code> on that instance. + +<p>Note that the program above depends on the fact that the +<code>Hello</code> class is never loaded before <code>toClass()</code> +is invoked. If not, the JVM would load the original +<code>Hello</code> class before <code>toClass()</code> requests to +load the modified <code>Hello</code> class. Hence loading the +modified <code>Hello</code> class would be failed +(<code>LinkageError</code> is thrown). For example, if +<code>main()</code> in <code>Test</code> is something like this: + +<ul><pre> +public static void main(String[] args) throws Exception { + Hello orig = new Hello(); + ClassPool cp = ClassPool.getDefault(); + CtClass cc = cp.get("Hello"); + : +} +</pre></ul> + +<p>then the original <code>Hello</code> class is loaded at the first +line of <code>main</code> and the call to <code>toClass()</code> +throws an exception since the class loader cannot load two different +versions of the <code>Hello</code> class at the same time. + +<p><em>If the program is running on some application server such as +JBoss and Tomcat,</em> the context class loader used by +<code>toClass()</code> might be inappropriate. In this case, you +would see an unexpected <code>ClassCastException</code>. To avoid +this exception, you must explicitly give an appropriate class loader +to <code>toClass()</code>. For example, if <code>bean</code> is your +session bean object, then the following code: + +<ul><pre>CtClass cc = ...; +Class c = cc.toClass(bean.getClass().getClassLoader()); +</pre></ul> + +<p>would work. You should give <code>toClass()</code> the class loader +that has loaded your program (in the above example, the class of +the <code>bean</code> object). + +<p><code>toClass()</code> is provided for convenience. If you need +more complex functionality, you should write your own class loader. + +<p><br> + +<h3>3.2 Class loading in Java</h3> + +<p>In Java, multiple class loaders can coexist and +each class loader creates its own name space. +Different class loaders can load different class files with the +same class name. The loaded two classes are regarded as different +ones. This feature enables us to run multiple application programs +on a single JVM even if these programs include different classes +with the same name. + +<ul> +<b>Note:</b> The JVM does not allow dynamically reloading a class. +Once a class loader loads a class, it cannot reload a modified +version of that class during runtime. Thus, you cannot alter +the definition of a class after the JVM loads it. +However, the JPDA (Java Platform Debugger Architecture) provides +limited ability for reloading a class. +See <a href="#hotswap">Section 3.6</a>. +</ul> + +<p>If the same class file is loaded by two distinct class loaders, +the JVM makes two distinct classes with the same name and definition. +The two classes are regarded as different ones. +Since the two classes are not identical, an instance of one class is +not assignable to a variable of the other class. The cast operation +between the two classes fails +and throws a <em><code>ClassCastException</code></em>. + +<p>For example, the following code snippet throws an exception: + +<ul><pre> +MyClassLoader myLoader = new MyClassLoader(); +Class clazz = myLoader.loadClass("Box"); +Object obj = clazz.newInstance(); +Box b = (Box)obj; // this always throws ClassCastException. +</pre></ul> + +<p> +The <code>Box</code> class is loaded by two class loaders. +Suppose that a class loader CL loads a class including this code snippet. +Since this code snippet refers to <code>MyClassLoader</code>, +<code>Class</code>, <code>Object</code>, and <code>Box</code>, +CL also loads these classes (unless it delegates to another class loader). +Hence the type of the variable <code>b</code> is the <code>Box</code> +class loaded by CL. +On the other hand, <code>myLoader</code> also loads the <code>Box</code> +class. The object <code>obj</code> is an instance of +the <code>Box</code> class loaded by <code>myLoader</code>. +Therefore, the last statement always throws a +<code>ClassCastException</code> since the class of <code>obj</code> is +a different verison of the <code>Box</code> class from one used as the +type of the variable <code>b</code>. + +<p>Multiple class loaders form a tree structure. +Each class loader except the bootstrap loader has a +parent class loader, which has normally loaded the class of that child +class loader. Since the request to load a class can be delegated along this +hierarchy of class loaders, a class may be loaded by a class loader that +you do not request the class loading. +Therefore, the class loader that has been requested to load a class C +may be different from the loader that actually loads the class C. +For distinction, we call the former loader <em>the initiator of C</em> +and we call the latter loader <em>the real loader of C</em>. + +<p> +Furthermore, if a class loader CL requested to load a class C +(the initiator of C) delegates +to the parent class loader PL, then the class loader CL is never requested +to load any classes referred to in the definition of the class C. +CL is not the initiator of those classes. +Instead, the parent class loader PL becomes their initiators +and it is requested to load them. +<em>The classes that the definition of a class C referes to are loaded by +the real loader of C.</em> + +<p>To understand this behavior, let's consider the following example. + +<ul><pre> +public class Point { // loaded by PL + private int x, y; + public int getX() { return x; } + : +} + +public class Box { // the initiator is L but the real loader is PL + private Point upperLeft, size; + public int getBaseX() { return upperLeft.x; } + : +} + +public class Window { // loaded by a class loader L + private Box box; + public int getBaseX() { return box.getBaseX(); } +}</pre></ul> + +<p>Suppose that a class <code>Window</code> is loaded by a class loader L. +Both the initiator and the real loader of <code>Window</code> are L. +Since the definition of <code>Window</code> refers to <code>Box</code>, +the JVM will request L to load <code>Box</code>. +Here, suppose that L delegates this task to the parent class loader PL. +The initiator of <code>Box</code> is L but the real loader is PL. +In this case, the initiator of <code>Point</code> is not L but PL +since it is the same as the real loader of <code>Box</code>. +Thus L is never requested to load <code>Point</code>. + +<p>Next, let's consider a slightly modified example. + +<ul><pre> +public class Point { + private int x, y; + public int getX() { return x; } + : +} + +public class Box { // the initiator is L but the real loader is PL + private Point upperLeft, size; + public Point getSize() { return size; } + : +} + +public class Window { // loaded by a class loader L + private Box box; + public boolean widthIs(int w) { + Point p = box.getSize(); + return w == p.getX(); + } +}</pre></ul> + +<p>Now, the definition of <code>Window</code> also refers to +<code>Point</code>. In this case, the class loader L must +also delegate to PL if it is requested to load <code>Point</code>. +<em>You must avoid having two class loaders doubly load the same +class.</em> One of the two loaders must delegate to +the other. + +<p> +If L does not delegate to PL when <code>Point</code> +is loaded, <code>widthIs()</code> would throw a ClassCastException. +Since the real loader of <code>Box</code> is PL, +<code>Point</code> referred to in <code>Box</code> is also loaded by PL. +Therefore, the resulting value of <code>getSize()</code> +is an instance of <code>Point</code> loaded by PL +whereas the type of the variable <code>p</code> in <code>widthIs()</code> +is <code>Point</code> loaded by L. +The JVM regards them as distinct types and thus it throws an exception +because of type mismatch. + +<p>This behavior is somewhat inconvenient but necessary. +If the following statement: + +<ul><pre> +Point p = box.getSize(); +</pre></ul> + +<p>did not throw an exception, +then the programmer of <code>Window</code> could break the encapsulation +of <code>Point</code> objects. +For example, the field <code>x</code> +is private in <code>Point</code> loaded by PL. +However, the <code>Window</code> class could +directly access the value of <code>x</code> +if L loads <code>Point</code> with the following definition: + +<ul><pre> +public class Point { + public int x, y; // not private + public int getX() { return x; } + : +} +</pre></ul> + +<p> +For more details of class loaders in Java, the following paper would +be helpful: + +<ul>Sheng Liang and Gilad Bracha, +"Dynamic Class Loading in the Java Virtual Machine", +<br><i>ACM OOPSLA'98</i>, pp.36-44, 1998.</ul> + +<p><br> + +<h3>3.3 Using <code>javassist.Loader</code></h3> + +<p>Javassist provides a class loader +<code>javassist.Loader</code>. This class loader uses a +<code>javassist.ClassPool</code> object for reading a class file. + +<p>For example, <code>javassist.Loader</code> can be used for loading +a particular class modified with Javassist. + +<ul><pre> +import javassist.*; +import test.Rectangle; + +public class Main { + public static void main(String[] args) throws Throwable { + ClassPool pool = ClassPool.getDefault(); + Loader cl = new Loader(pool); + + CtClass ct = pool.get("test.Rectangle"); + ct.setSuperclass(pool.get("test.Point")); + + Class c = cl.loadClass("test.Rectangle"); + Object rect = c.newInstance(); + : + } +} +</pre></ul> + +<p>This program modifies a class <code>test.Rectangle</code>. The +superclass of <code>test.Rectangle</code> is set to a +<code>test.Point</code> class. Then this program loads the modified +class, and creates a new instance of the +<code>test.Rectangle</code> class. + +<p>If the users want to modify a class on demand when it is loaded, +the users can add an event listener to a <code>javassist.Loader</code>. +The added event listener is +notified when the class loader loads a class. +The event-listener class must implement the following interface: + +<ul><pre>public interface Translator { + public void start(ClassPool pool) + throws NotFoundException, CannotCompileException; + public void onLoad(ClassPool pool, String classname) + throws NotFoundException, CannotCompileException; +}</pre></ul> + +<p>The method <code>start()</code> is called when this event listener +is added to a <code>javassist.Loader</code> object by +<code>addTranslator()</code> in <code>javassist.Loader</code>. The +method <code>onLoad()</code> is called before +<code>javassist.Loader</code> loads a class. <code>onLoad()</code> +can modify the definition of the loaded class. + +<p>For example, the following event listener changes all classes +to public classes just before they are loaded. + +<ul><pre>public class MyTranslator implements Translator { + void start(ClassPool pool) + throws NotFoundException, CannotCompileException {} + void onLoad(ClassPool pool, String classname) + throws NotFoundException, CannotCompileException + { + CtClass cc = pool.get(classname); + cc.setModifiers(Modifier.PUBLIC); + } +}</pre></ul> + +<p>Note that <code>onLoad()</code> does not have to call +<code>toBytecode()</code> or <code>writeFile()</code> since +<code>javassist.Loader</code> calls these methods to obtain a class +file. + +<p>To run an application class <code>MyApp</code> with a +<code>MyTranslator</code> object, write a main class as following: + +<ul><pre> +import javassist.*; + +public class Main2 { + public static void main(String[] args) throws Throwable { + Translator t = new MyTranslator(); + ClassPool pool = ClassPool.getDefault(); + Loader cl = new Loader(); + cl.addTranslator(pool, t); + cl.run("MyApp", args); + } +} +</pre></ul> + +<p>To run this program, do: + +<ul><pre> +% java Main2 <i>arg1</i> <i>arg2</i>... +</pre></ul> + +<p>The class <code>MyApp</code> and the other application classes +are translated by <code>MyTranslator</code>. + +<p>Note that <em>application</em> classes like <code>MyApp</code> cannot +access the <em>loader</em> classes such as <code>Main2</code>, +<code>MyTranslator</code>, and <code>ClassPool</code> because they +are loaded by different loaders. The application classes are loaded +by <code>javassist.Loader</code> whereas the loader classes such as +<code>Main2</code> are by the default Java class loader. + +<p><code>javassist.Loader</code> searches for classes in a different +order from <code>java.lang.ClassLoader</code>. +<code>ClassLoader</code> first delegates the loading operations to +the parent class loader and then attempts to load the classes +only if the parent class loader cannot find them. +On the other hand, +<code>javassist.Loader</code> attempts +to load the classes before delegating to the parent class loader. +It delegates only if: + +<ul><li>the classes are not found by calling <code>get()</code> on +a <code>ClassPool</code> object, or + +<p><li>the classes have been specified by using +<code>delegateLoadingOf()</code> +to be loaded by the parent class loader. +</ul> + +<p>This search order allows loading modified classes by Javassist. +However, it delegates to the parent class loader if it fails +to find modified classes for some reason. Once a class is loaded by +the parent class loader, the other classes referred to in that class will be +also loaded by the parent class loader and thus they are never modified. +Recall that all the classes referred to in a class C are loaded by the +real loader of C. +<em>If your program fails to load a modified class,</em> you should +make sure whether all the classes using that class have been loaded by +<code>javassist.Loader</code>. + +<p><br> + +<h3>3.4 Writing a class loader</h3> + +<p>A simple class loader using Javassist is as follows: + +<ul><pre>import javassist.*; + +public class SampleLoader extends ClassLoader { + /* Call MyApp.main(). + */ + public static void main(String[] args) throws Throwable { + SampleLoader s = new SampleLoader(); + Class c = s.loadClass("MyApp"); + c.getDeclaredMethod("main", new Class[] { String[].class }) + .invoke(null, new Object[] { args }); + } + + private ClassPool pool; + + public SampleLoader() throws NotFoundException { + pool = new ClassPool(); + pool.insertClassPath("./class"); // <em>MyApp.class must be there.</em> + } + + /* Finds a specified class. + * The bytecode for that class can be modified. + */ + protected Class findClass(String name) throws ClassNotFoundException { + try { + CtClass cc = pool.get(name); + // <em>modify the CtClass object here</em> + byte[] b = cc.toBytecode(); + return defineClass(name, b, 0, b.length); + } catch (NotFoundException e) { + throw new ClassNotFoundException(); + } catch (IOException e) { + throw new ClassNotFoundException(); + } catch (CannotCompileException e) { + throw new ClassNotFoundException(); + } + } +}</pre></ul> + +<p>The class <code>MyApp</code> is an application program. +To execute this program, first put the class file under the +<code>./class</code> directory, which must <em>not</em> be included +in the class search path. Otherwise, <code>MyApp.class</code> would +be loaded by the default system class loader, which is the parent +loader of <code>SampleLoader</code>. +The directory name <code>./class</code> is specified by +<code>insertClassPath()</code> in the constructor. +You can choose a different name instead of <code>./class</code> if you want. +Then do as follows: + +<ul><code>% java SampleLoader</code></ul> + +<p>The class loader loads the class <code>MyApp</code> +(<code>./class/MyApp.class</code>) and calls +<code>MyApp.main()</code> with the command line parameters. + +<p>This is the simplest way of using Javassist. However, if you write +a more complex class loader, you may need detailed knowledge of +Java's class loading mechanism. For example, the program above puts the +<code>MyApp</code> class in a name space separated from the name space +that the class <code>SampleLoader</code> belongs to because the two +classes are loaded by different class loaders. +Hence, the +<code>MyApp</code> class cannot directly access the class +<code>SampleLoader</code>. + +<p><br> + +<h3>3.5 Modifying a system class</h3> + +<p>The system classes like <code>java.lang.String</code> cannot be +loaded by a class loader other than the system class loader. +Therefore, <code>SampleLoader</code> or <code>javassist.Loader</code> +shown above cannot modify the system classes at loading time. + +<p>If your application needs to do that, the system classes must be +<em>statically</em> modified. For example, the following program +adds a new field <code>hiddenValue</code> to <code>java.lang.String</code>: + +<ul><pre>ClassPool pool = ClassPool.getDefault(); +CtClass cc = pool.get("java.lang.String"); +cc.addField(new CtField(CtClass.intType, "hiddenValue", cc)); +cc.writeFile(".");</pre></ul> + +<p>This program produces a file <code>"./java/lang/String.class"</code>. + +<p>To run your program <code>MyApp</code> +with this modified <code>String</code> class, do as follows: + +<ul><pre> +% java -Xbootclasspath/p:. MyApp <i>arg1</i> <i>arg2</i>... +</pre></ul> + +<p>Suppose that the definition of <code>MyApp</code> is as follows: + +<ul><pre>public class MyApp { + public static void main(String[] args) throws Exception { + System.out.println(String.class.getField("hiddenValue").getName()); + } +}</pre></ul> + +<p>If the modified <code>String</code> class is correctly loaded, +<code>MyApp</code> prints <code>hiddenValue</code>. + +<p><i>Note: Applications that use this technique for the purpose of +overriding a system class in <code>rt.jar</code> should not be +deployed as doing so would contravene the Java 2 Runtime Environment +binary code license.</i> + +<p><br> + +<a name="hotswap"> +<h3>3.6 Reloading a class at runtime</h3></a> + +<p>If the JVM is launched with the JPDA (Java Platform Debugger +Architecture) enabled, a class is dynamically reloadable. After the +JVM loads a class, the old version of the class definition can be +unloaded and a new one can be reloaded again. That is, the definition +of that class can be dynamically modified during runtime. However, +the new class definition must be somewhat compatible to the old one. +<em>The JVM does not allow schema changes between the two versions.</em> +They have the same set of methods and fields. + +<p>Javassist provides a convenient class for reloading a class at runtime. +For more information, see the API documentation of +<code>javassist.tools.HotSwapper</code>. + +<p><br> + +<a href="tutorial2.html">Next page</a> + +<hr> +Java(TM) is a trademark of Sun Microsystems, Inc.<br> +Copyright (C) 2000-2010 by Shigeru Chiba, All rights reserved. +</body> +</html> diff --git a/tutorial/tutorial2.html b/tutorial/tutorial2.html new file mode 100644 index 0000000..445fc90 --- /dev/null +++ b/tutorial/tutorial2.html @@ -0,0 +1,1627 @@ +<html> +<head> + <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> + <title>Javassist Tutorial</title> + <link rel="stylesheet" type="text/css" href="brown.css"> +</head> + +<body> + +<div align="right">Getting Started with Javassist</div> + +<div align="left"><a href="tutorial.html">Previous page</a></div> +<div align="right"><a href="tutorial3.html">Next page</a></div> + +<p> +<a href="#intro">4. Introspection and customization</a> +<ul> +<li><a href="#before">Inserting source text at the beginning/end of a method body</a> +<br><li><a href="#alter">Altering a method body</a> +<br><li><a href="#add">Adding a new method or field</a> +<br><li><a href="#runtime">Runtime support classes</a> +<br><li><a href="#annotation">Annotations</a> +<br><li><a href="#import">Import</a> +<br><li><a href="#limit">Limitations</a> +</ul> + +<p><br> + +<a name="intro"> +<h2>4. Introspection and customization</h2> + +<p><code>CtClass</code> provides methods for introspection. The +introspective ability of Javassist is compatible with that of +the Java reflection API. <code>CtClass</code> provides +<code>getName()</code>, <code>getSuperclass()</code>, +<code>getMethods()</code>, and so on. +<code>CtClass</code> also provides methods for modifying a class +definition. It allows to add a new field, constructor, and method. +Instrumenting a method body is also possible. + +<p> +Methods are represented by <code>CtMethod</code> objects. +<code>CtMethod</code> provides several methods for modifying +the definition of the method. Note that if a method is inherited +from a super class, then +the same <code>CtMethod</code> object +that represents the inherited method represents the method declared +in that super class. +A <code>CtMethod</code> object corresponds to every method declaration. + +<p> +For example, if class <code>Point</code> declares method <code>move()</code> +and a subclass <code>ColorPoint</code> of <code>Point</code> does +not override <code>move()</code>, the two <code>move()</code> methods +declared in <code>Point</code> and inherited in <code>ColorPoint</code> +are represented by the identical <code>CtMethod</code> object. +If the method definition represented by this +<code>CtMethod</code> object is modified, the modification is +reflected on both the methods. +If you want to modify only the <code>move()</code> method in +<code>ColorPoint</code>, you first have to add to <code>ColorPoint</code> +a copy of the <code>CtMethod</code> object representing <code>move()</code> +in <code>Point</code>. A copy of the the <code>CtMethod</code> object +can be obtained by <code>CtNewMethod.copy()</code>. + + +<p><hr width="40%"> + +<ul> +Javassist does not allow to remove a method or field, but it allows +to change the name. So if a method is not necessary any more, it should be +renamed and changed to be a private method by calling +<code>setName()</code> +and <code>setModifiers()</code> declared in <code>CtMethod</code>. + +<p>Javassist does not allow to add an extra parameter to an existing +method, either. Instead of doing that, a new method receiving the +extra parameter as well as the other parameters should be added to the +same class. For example, if you want to add an extra <code>int</code> +parameter <code>newZ</code> to a method: + +<ul><pre>void move(int newX, int newY) { x = newX; y = newY; }</pre></ul> + +<p>in a <code>Point</code> class, then you should add the following +method to the <code>Point</code> class: + +<ul><pre>void move(int newX, int newY, int newZ) { + // do what you want with newZ. + move(newX, newY); +}</pre></ul> + +</ul> + +<p><hr width="40%"> + +<p>Javassist also provides low-level API for directly editing a raw +class file. For example, <code>getClassFile()</code> in +<code>CtClass</code> returns a <code>ClassFile</code> object +representing a raw class file. <code>getMethodInfo()</code> in +<code>CtMethod</code> returns a <code>MethodInfo</code> object +representing a <code>method_info</code> structure included in a class +file. The low-level API uses the vocabulary from the Java Virtual +machine specification. The users must have the knowledge about class +files and bytecode. For more details, the users should see the +<a href="tutorial3.html#intro"><code>javassist.bytecode</code> package</a>. + +<p>The class files modified by Javassist requires the +<code>javassist.runtime</code> package for runtime support +only if some special identifiers starting with <code>$</code> +are used. Those special identifiers are described below. +The class files modified without those special identifiers +do not need the <code>javassist.runtime</code> package or any +other Javassist packages at runtime. +For more details, see the API documentation +of the <code>javassist.runtime</code> package. + +<p><br> + +<a name="before"> +<h3>4.1 Inserting source text at the beginning/end of a method body</h3> + +<p><code>CtMethod</code> and <code>CtConstructor</code> provide +methods <code>insertBefore()</code>, <code>insertAfter()</code>, and +<code>addCatch()</code>. They are used for inserting a code fragment +into the body of an existing method. The users can specify those code +fragments with <em>source text</em> written in Java. +Javassist includes a simple Java compiler for processing source +text. It receives source text +written in Java and compiles it into Java bytecode, which will be +<em>inlined</em> into a method body. + +<p> +Inserting a code fragment at the position specified by a line number +is also possible +(if the line number table is contained in the class file). +<code>insertAt()</code> in <code>CtMethod</code> and +<code>CtConstructor</code> takes source text and a line number in the source +file of the original class definition. +It compiles the source text and inserts the compiled code at the line number. + +<p>The methods <code>insertBefore()</code>, <code>insertAfter()</code>, +<code>addCatch()</code>, and <code>insertAt()</code> +receive a <code>String</code> object representing +a statement or a block. A statement is a single control structure like +<code>if</code> and <code>while</code> or an expression ending with +a semi colon (<code>;</code>). A block is a set of +statements surrounded with braces <code>{}</code>. +Hence each of the following lines is an example of valid statement or block: + +<ul><pre>System.out.println("Hello"); +{ System.out.println("Hello"); } +if (i < 0) { i = -i; } +</pre></ul> + +<p>The statement and the block can refer to fields and methods. +They can also refer to the parameters +to the method that they are inserted into +if that method was compiled with the -g option +(to include a local variable attribute in the class file). +Otherwise, they must access the method parameters through the special +variables <code>$0</code>, <code>$1</code>, <code>$2</code>, ... described +below. +<em>Accessing local variables declared in the method is not allowed</em> +although declaring a new local variable in the block is allowed. +However, <code>insertAt()</code> allows the statement and the block +to access local variables +if these variables are available at the specified line number +and the target method was compiled with the -g option. + + +<!-- +<p><center><table border=8 cellspacing=0 bordercolor="#cfcfcf"> +<tr><td bgcolor="#cfcfcf"> +<b>Tip:</b> +<br>    Local variables are not accessible.   +</td></tr> +</table></center> +--> + +<p>The <code>String</code> object passed to the methods +<code>insertBefore()</code>, <code>insertAfter()</code>, +<code>addCatch()</code>, and <code>insertAt()</code> are compiled by +the compiler included in Javassist. +Since the compiler supports language extensions, +several identifiers starting with <code>$</code> +have special meaning: + +<ul><table border=0> +<tr> +<td><code>$0</code>, <code>$1</code>, <code>$2</code>, ...    </td> +<td><code>this</code> and actual parameters</td> +</tr> + +<tr> +<td><code>$args</code></td> +<td>An array of parameters. +The type of <code>$args</code> is <code>Object[]</code>. +</td> +</tr> + +<tr> +<td><code>$$</code></td> +<td rowspan=2>All actual parameters.<br> +For example, <code>m($$)</code> is equivalent to +<code>m($1,$2,</code>...<code>)</code></td> +</tr> + +<tr><td> </td></tr> + +<tr> +<td><code>$cflow(</code>...<code>)</code></td> +<td><code>cflow</code> variable</td> +</tr> + +<tr> +<td><code>$r</code></td> +<td>The result type. It is used in a cast expression.</td> +</tr> + +<tr> +<td><code>$w</code></td> +<td>The wrapper type. It is used in a cast expression.</td> +</tr> + +<tr> +<td><code>$_</code></td> +<td>The resulting value</td> +</tr> + +<tr> +<td><code>$sig</code></td> +<td>An array of <code>java.lang.Class</code> objects representing +the formal parameter types. +</td> +</tr> + +<tr> +<td><code>$type</code></td> +<td>A <code>java.lang.Class</code> object representing +the formal result type.</td> +</tr> + +<tr> +<td><code>$class</code></td> +<td>A <code>java.lang.Class</code> object representing +the class currently edited.</td> +</tr> + +</table> +</ul> + +<h4>$0, $1, $2, ...</h4> + +<p>The parameters passed to the target method +are accessible with +<code>$1</code>, <code>$2</code>, ... instead of +the original parameter names. +<code>$1</code> represents the +first parameter, <code>$2</code> represents the second parameter, and +so on. The types of those variables are identical to the parameter +types. +<code>$0</code> is +equivalent to <code>this</code>. If the method is static, +<code>$0</code> is not available. + +<p>These variables are used as following. Suppose that a class +<code>Point</code>: + +<pre><ul>class Point { + int x, y; + void move(int dx, int dy) { x += dx; y += dy; } +} +</ul></pre> + +<p>To print the values of <code>dx</code> and <code>dy</code> +whenever the method <code>move()</code> is called, execute this +program: + +<ul><pre>ClassPool pool = ClassPool.getDefault(); +CtClass cc = pool.get("Point"); +CtMethod m = cc.getDeclaredMethod("move"); +m.insertBefore("{ System.out.println($1); System.out.println($2); }"); +cc.writeFile(); +</pre></ul> + +<p>Note that the source text passed to <code>insertBefore()</code> is +surrounded with braces <code>{}</code>. +<code>insertBefore()</code> accepts only a single statement or a block +surrounded with braces. + +<p>The definition of the class <code>Point</code> after the +modification is like this: + +<pre><ul>class Point { + int x, y; + void move(int dx, int dy) { + { System.out.println(dx); System.out.println(dy); } + x += dx; y += dy; + } +} +</ul></pre> + +<p><code>$1</code> and <code>$2</code> are replaced with +<code>dx</code> and <code>dy</code>, respectively. + +<p><code>$1</code>, <code>$2</code>, <code>$3</code> ... are +updatable. If a new value is assigend to one of those variables, +then the value of the parameter represented by that variable is +also updated. + + +<h4>$args</h4> + +<p>The variable <code>$args</code> represents an array of all the +parameters. The type of that variable is an array of class +<code>Object</code>. If a parameter type is a primitive type such as +<code>int</code>, then the parameter value is converted into a wrapper +object such as <code>java.lang.Integer</code> to store in +<code>$args</code>. Thus, <code>$args[0]</code> is equivalent to +<code>$1</code> unless the type of the first parameter is a primitive +type. Note that <code>$args[0]</code> is not equivalent to +<code>$0</code>; <code>$0</code> represents <code>this</code>. + +<p>If an array of <code>Object</code> is assigned to +<code>$args</code>, then each element of that array is +assigned to each parameter. If a parameter type is a primitive +type, the type of the corresponding element must be a wrapper type. +The value is converted from the wrapper type to the primitive type +before it is assigned to the parameter. + +<h4>$$</h4> + +<p>The variable <code>$$</code> is abbreviation of a list of +all the parameters separated by commas. +For example, if the number of the parameters +to method <code>move()</code> is three, then + +<ul><pre>move($$)</pre></ul> + +<p>is equivalent to this: + +<ul><pre>move($1, $2, $3)</pre></ul> + +<p>If <code>move()</code> does not take any parameters, +then <code>move($$)</code> is +equivalent to <code>move()</code>. + +<p><code>$$</code> can be used with another method. +If you write an expression: + +<ul><pre>exMove($$, context)</pre></ul> + +<p>then this expression is equivalent to: + +<ul><pre>exMove($1, $2, $3, context)</pre></ul> + +<p>Note that <code>$$</code> enables generic notation of method call +with respect to the number of parameters. +It is typically used with <code>$proceed</code> shown later. + +<h4>$cflow</h4> + +<p><code>$cflow</code> means "control flow". +This read-only variable returns the depth of the recursive calls +to a specific method. + +<p>Suppose that the method shown below is represented by a +<code>CtMethod</code> object <code>cm</code>: + +<ul><pre>int fact(int n) { + if (n <= 1) + return n; + else + return n * fact(n - 1); +}</pre></ul> + +<p>To use <code>$cflow</code>, first declare that <code>$cflow</code> +is used for monitoring calls to the method <code>fact()</code>: + +<ul><pre>CtMethod cm = ...; +cm.useCflow("fact");</pre></ul> + +<p>The parameter to <code>useCflow()</code> is the identifier of the +declared <code>$cflow</code> variable. Any valid Java name can be +used as the identifier. Since the identifier can also include +<code>.</code> (dot), for example, <code>"my.Test.fact"</code> +is a valid identifier. + +<p>Then, <code>$cflow(fact)</code> represents the depth of the +recursive calls to the method specified by <code>cm</code>. The value +of <code>$cflow(fact)</code> is 0 (zero) when the method is +first called whereas it is 1 when the method is recursively called +within the method. For example, + +<ul><pre> +cm.insertBefore("if ($cflow(fact) == 0)" + + " System.out.println(\"fact \" + $1);"); +</pre></ul> + +<p>translates the method <code>fact()</code> so that it shows the +parameter. Since the value of <code>$cflow(fact)</code> is checked, +the method <code>fact()</code> does not show the parameter if it is +recursively called within <code>fact()</code>. + +<p>The value of <code>$cflow</code> is the number of stack frames +associated with the specified method <code>cm</code> +under the current topmost +stack frame for the current thread. <code>$cflow</code> is also +accessible within a method different from the specified method +<code>cm</code>. + +<h4>$r</h4> + +<p><code>$r</code> represents the result type (return type) of the method. +It must be used as the cast type in a cast expression. +For example, this is a typical use: + +<ul><pre>Object result = ... ; +$_ = ($r)result;</pre></ul> + +<p>If the result type is a primitive type, then <code>($r)</code> +follows special semantics. First, if the operand type of the cast +expression is a primitive type, <code>($r)</code> works as a normal +cast operator to the result type. +On the other hand, if the operand type is a wrapper type, +<code>($r)</code> converts from the wrapper type to the result type. +For example, if the result type is <code>int</code>, then +<code>($r)</code> converts from <code>java.lang.Integer</code> to +<code>int</code>. + +<p>If the result type is <code>void</code>, then +<code>($r)</code> does not convert a type; it does nothing. +However, if the operand is a call to a <code>void</code> method, +then <code>($r)</code> results in <code>null</code>. For example, +if the result type is <code>void</code> and +<code>foo()</code> is a <code>void</code> method, then + +<ul><pre>$_ = ($r)foo();</pre></ul> + +<p>is a valid statement. + +<p>The cast operator <code>($r)</code> is also useful in a +<code>return</code> statement. Even if the result type is +<code>void</code>, the following <code>return</code> statement is valid: + +<ul><pre>return ($r)result;</pre></ul> + +<p>Here, <code>result</code> is some local variable. +Since <code>($r)</code> is specified, the resulting value is +discarded. +This <code>return</code> statement is regarded as the equivalent +of the <code>return</code> statement without a resulting value: + +<ul><pre>return;</pre></ul> + +<h4>$w</h4> + +<p><code>$w</code> represents a wrapper type. +It must be used as the cast type in a cast expression. +<code>($w)</code> converts from a primitive type to the corresponding +wrapper type. + +The following code is an example: + +<ul><pre>Integer i = ($w)5;</pre></ul> + +<p>The selected wrapper type depends on the type of the expression +following <code>($w)</code>. If the type of the expression is +<code>double</code>, then the wrapper type is <code>java.lang.Double</code>. + +<p>If the type of the expression following <code>($w)</code> is not +a primitive type, then <code>($w)</code> does nothing. + +<h4>$_</h4> + +<p><code>insertAfter()</code> in <code>CtMethod</code> and +<code>CtConstructor</code> inserts the +compiled code at the end of the method. In the statement given to +<code>insertAfter()</code>, not only the variables shown above such as +<code>$0</code>, <code>$1</code>, ... but also <code>$_</code> is +available. + +<p>The variable <code>$_</code> represents the resulting value of the +method. The type of that variable is the type of the result type (the +return type) of the method. If the result type is <code>void</code>, +then the type of <code>$_</code> is <code>Object</code> and the value +of <code>$_</code> is <code>null</code>. + +<p>Although the compiled code inserted by <code>insertAfter()</code> +is executed just before the control normally returns from the method, +it can be also executed when an exception is thrown from the method. +To execute it when an exception is thrown, the second parameter +<code>asFinally</code> to <code>insertAfter()</code> must be +<code>true</code>. + +<p>If an exception is thrown, the compiled code inserted by +<code>insertAfter()</code> is executed as a <code>finally</code> +clause. The value of <code>$_</code> is <code>0</code> or +<code>null</code> in the compiled code. After the execution of the +compiled code terminates, the exception originally thrown is re-thrown +to the caller. Note that the value of <code>$_</code> is never thrown +to the caller; it is rather discarded. + +<h4>$sig</h4> + +<p>The value of <code>$sig</code> is an array of +<code>java.lang.Class</code> objects that represent the formal +parameter types in declaration order. + +<h4>$type</h4> + +<p>The value of <code>$type</code> is an <code>java.lang.Class</code> +object representing the formal type of the result value. This +variable refers to <code>Void.class</code> if this is a constructor. + +<h4>$class</h4> + +<p>The value of <code>$class</code> is an <code>java.lang.Class</code> +object representing the class in which the edited method is declared. +This represents the type of <code>$0</code>. + +<h4>addCatch()</h4> + +<p><code>addCatch()</code> inserts a code fragment into a method body +so that the code fragment is executed when the method body throws +an exception and the control returns to the caller. In the source +text representing the inserted code fragment, the exception value +is referred to with the special variable <code>$e</code>. + +<p>For example, this program: + +<ul><pre> +CtMethod m = ...; +CtClass etype = ClassPool.getDefault().get("java.io.IOException"); +m.addCatch("{ System.out.println($e); throw $e; }", etype); +</pre></ul> + +<p>translates the method body represented by <code>m</code> into +something like this: + +<ul><pre> +try { + <font face="serif"><em>the original method body</em></font> +} +catch (java.io.IOException e) { + System.out.println(e); + throw e; +} +</pre></ul> + +<p>Note that the inserted code fragment must end with a +<code>throw</code> or <code>return</code> statement. + +<p><br> + +<a name="alter"> +<h3>4.2 Altering a method body</h3> + +<p><code>CtMethod</code> and <code>CtConstructor</code> provide +<code>setBody()</code> for substituting a whole +method body. They compile the given source text into Java bytecode +and substitutes it for the original method body. If the given source +text is <code>null</code>, the substituted body includes only a +<code>return</code> statement, which returns zero or null unless the +result type is <code>void</code>. + +<p>In the source text given to <code>setBody()</code>, the identifiers +starting with <code>$</code> have special meaning + +<ul><table border=0> +<tr> +<td><code>$0</code>, <code>$1</code>, <code>$2</code>, ...    </td> +<td><code>this</code> and actual parameters</td> +</tr> + +<tr> +<td><code>$args</code></td> +<td>An array of parameters. +The type of <code>$args</code> is <code>Object[]</code>. +</td> +</tr> + +<tr> +<td><code>$$</code></td> +<td>All actual parameters.<br> +</tr> + +<tr> +<td><code>$cflow(</code>...<code>)</code></td> +<td><code>cflow</code> variable</td> +</tr> + +<tr> +<td><code>$r</code></td> +<td>The result type. It is used in a cast expression.</td> +</tr> + +<tr> +<td><code>$w</code></td> +<td>The wrapper type. It is used in a cast expression.</td> +</tr> + +<tr> +<td><code>$sig</code></td> +<td>An array of <code>java.lang.Class</code> objects representing +the formal parameter types. +</td> +</tr> + +<tr> +<td><code>$type</code></td> +<td>A <code>java.lang.Class</code> object representing +the formal result type.</td> +</tr> + +<tr> +<td><code>$class</code></td> +<td rowspan=2>A <code>java.lang.Class</code> object representing +the class that declares the method<br> +currently edited (the type of $0).</td> +</tr> + +<tr><td> </td></tr> + +</table> +</ul> + +Note that <code>$_</code> is not available. + +<h4>Substituting source text for an existing expression</h4> + +<p>Javassist allows modifying only an expression included in a method body. +<code>javassist.expr.ExprEditor</code> is a class +for replacing an expression in a method body. +The users can define a subclass of <code>ExprEditor</code> +to specify how an expression is modified. + +<p>To run an <code>ExprEditor</code> object, the users must +call <code>instrument()</code> in <code>CtMethod</code> or +<code>CtClass</code>. + +For example, + +<ul><pre> +CtMethod cm = ... ; +cm.instrument( + new ExprEditor() { + public void edit(MethodCall m) + throws CannotCompileException + { + if (m.getClassName().equals("Point") + && m.getMethodName().equals("move")) + m.replace("{ $1 = 0; $_ = $proceed($$); }"); + } + }); +</pre></ul> + +<p>searches the method body represented by <code>cm</code> and +replaces all calls to <code>move()</code> in class <code>Point</code> +with a block: + +<ul><pre>{ $1 = 0; $_ = $proceed($$); } +</pre></ul> + +<p>so that the first parameter to <code>move()</code> is always 0. +Note that the substituted code is not an expression but +a statement or a block. It cannot be or contain a try-catch statement. + +<p>The method <code>instrument()</code> searches a method body. +If it finds an expression such as a method call, field access, and object +creation, then it calls <code>edit()</code> on the given +<code>ExprEditor</code> object. The parameter to <code>edit()</code> +is an object representing the found expression. The <code>edit()</code> +method can inspect and replace the expression through that object. + +<p>Calling <code>replace()</code> on the parameter to <code>edit()</code> +substitutes the given statement or block for the expression. If the given +block is an empty block, that is, if <code>replace("{}")</code> +is executed, then the expression is removed from the method body. + +If you want to insert a statement (or a block) before/after the +expression, a block like the following should be passed to +<code>replace()</code>: + +<ul><pre> +{ <em>before-statements;</em> + $_ = $proceed($$); + <em>after-statements;</em> } +</pre></ul> + +<p>whichever the expression is either a method call, field access, +object creation, or others. The second statement could be: + +<ul><pre>$_ = $proceed();</pre></ul> + +<p>if the expression is read access, or + +<ul><pre>$proceed($$);</pre></ul> + +<p>if the expression is write access. + +<p>Local variables available in the target expression is +also available in the source text passed to <code>replace()</code> +if the method searched by <code>instrument()</code> was compiled +with the -g option (the class file includes a local variable +attribute). + +<h4>javassist.expr.MethodCall</h4> + +<p>A <code>MethodCall</code> object represents a method call. +The method <code>replace()</code> in +<code>MethodCall</code> substitutes a statement or +a block for the method call. +It receives source text representing the substitued statement or +block, in which the identifiers starting with <code>$</code> +have special meaning as in the source text passed to +<code>insertBefore()</code>. + +<ul><table border=0> +<tr> +<td><code>$0</code></td> +<td rowspan=3> +The target object of the method call.<br> +This is not equivalent to <code>this</code>, which represents +the caller-side <code>this</code> object.<br> +<code>$0</code> is <code>null</code> if the method is static. +</td> +</tr> + +<tr><td> </td></tr> + +<tr><td> </td></tr> + +<tr> +<td><code>$1</code>, <code>$2</code>, ...    </td> +<td> +The parameters of the method call. +</td> +</tr> + +<tr><td> +<code>$_</code></td> +<td>The resulting value of the method call.</td> +</tr> + +<tr><td><code>$r</code></td> +<td>The result type of the method call.</td> +</tr> + +<tr><td><code>$class</code>    </td> +<td>A <code>java.lang.Class</code> object representing +the class declaring the method. +</td> +</tr> + +<tr><td><code>$sig</code>    </td> +<td>An array of <code>java.lang.Class</code> objects representing +the formal parameter types.</td> +</tr> + +<tr><td><code>$type</code>    </td> +<td>A <code>java.lang.Class</code> object representing +the formal result type.</td> +</tr> + +<tr><td><code>$proceed</code>    </td> +<td>The name of the method originally called +in the expression.</td> +</tr> + +</table> +</ul> + +<p>Here the method call means the one represented by the +<code>MethodCall</code> object. + +<p>The other identifiers such as <code>$w</code>, +<code>$args</code> and <code>$$</code> +are also available. + +<p>Unless the result type of the method call is <code>void</code>, +a value must be assigned to +<code>$_</code> in the source text and the type of <code>$_</code> +is the result type. +If the result type is <code>void</code>, the type of <code>$_</code> +is <code>Object</code> and the value assigned to <code>$_</code> +is ignored. + +<p><code>$proceed</code> is not a <code>String</code> value but special +syntax. It must be followed by an argument list surrounded by parentheses +<code>( )</code>. + +<h4>javassist.expr.ConstructorCall</h4> + +<p>A <code>ConstructorCall</code> object represents a constructor call +such as <code>this()</code> and <code>super</code> included in a constructor +body. +The method <code>replace()</code> in +<code>ConstructorCall</code> substitutes a statement or +a block for the constructor call. +It receives source text representing the substituted statement or +block, in which the identifiers starting with <code>$</code> +have special meaning as in the source text passed to +<code>insertBefore()</code>. + +<ul><table border=0> +<tr> +<td><code>$0</code></td> +<td> +The target object of the constructor call. +This is equivalent to <code>this</code>. +</td> +</tr> + +<tr> +<td><code>$1</code>, <code>$2</code>, ...    </td> +<td> +The parameters of the constructor call. +</td> +</tr> + +<tr><td><code>$class</code>    </td> +<td>A <code>java.lang.Class</code> object representing +the class declaring the constructor. +</td> +</tr> + +<tr><td><code>$sig</code>    </td> +<td>An array of <code>java.lang.Class</code> objects representing +the formal parameter types.</td> +</tr> + +<tr><td><code>$proceed</code>    </td> +<td>The name of the constructor originally called +in the expression.</td> +</tr> + +</table> +</ul> + +<p>Here the constructor call means the one represented by the +<code>ConstructorCall</code> object. + +<p>The other identifiers such as <code>$w</code>, +<code>$args</code> and <code>$$</code> +are also available. + +<p>Since any constructor must call either a constructor of the super +class or another constructor of the same class, +the substituted statement must include a constructor call, +normally a call to <code>$proceed()</code>. + +<p><code>$proceed</code> is not a <code>String</code> value but special +syntax. It must be followed by an argument list surrounded by parentheses +<code>( )</code>. + +<h4>javassist.expr.FieldAccess</h4> + +<p>A <code>FieldAccess</code> object represents field access. +The method <code>edit()</code> in <code>ExprEditor</code> +receives this object if field access is found. +The method <code>replace()</code> in +<code>FieldAccess</code> receives +source text representing the substitued statement or +block for the field access. + +<p> +In the source text, the identifiers starting with <code>$</code> +have special meaning: + +<ul><table border=0> +<tr> +<td><code>$0</code></td> +<td rowspan=3> +The object containing the field accessed by the expression. +This is not equivalent to <code>this</code>.<br> +<code>this</code> represents the object that the method including the +expression is invoked on.<br> +<code>$0</code> is <code>null</code> if the field is static. +</td> +</tr> + +<tr><td> </td></tr> + +<tr><td> </td></tr> + +<tr> +<td><code>$1</code></td> +<td rowspan=2> +The value that would be stored in the field +if the expression is write access. +<br>Otherwise, <code>$1</code> is not available. +</td> +</tr> + +<tr><td> </td></tr> + +<tr> +<td><code>$_</code></td> +<td rowspan=2> +The resulting value of the field access +if the expression is read access. +<br>Otherwise, the value stored in <code>$_</code> is discarded. +</td> +</tr> + +<tr><td> </td></tr> +<tr> +<td><code>$r</code></td> +<td rowspan=2> +The type of the field if the expression is read access. +<br>Otherwise, <code>$r</code> is <code>void</code>. +</td> +</tr> + +<tr><td> </td></tr> + +<tr><td><code>$class</code>    </td> +<td>A <code>java.lang.Class</code> object representing +the class declaring the field. +</td></tr> + +<tr><td><code>$type</code></td> +<td>A <code>java.lang.Class</code> object representing +the field type.</td> +</tr> + +<tr><td><code>$proceed</code>    </td> +<td>The name of a virtual method executing the original +field access. +.</td> +</tr> + +</table> +</ul> + +<p>The other identifiers such as <code>$w</code>, +<code>$args</code> and <code>$$</code> +are also available. + +<p>If the expression is read access, a value must be assigned to +<code>$_</code> in the source text. The type of <code>$_</code> +is the type of the field. + +<h4>javassist.expr.NewExpr</h4> + +<p>A <code>NewExpr</code> object represents object creation +with the <code>new</code> operator (not including array creation). +The method <code>edit()</code> in <code>ExprEditor</code> +receives this object if object creation is found. +The method <code>replace()</code> in +<code>NewExpr</code> receives +source text representing the substitued statement or +block for the object creation. + +<p> +In the source text, the identifiers starting with <code>$</code> +have special meaning: + +<ul><table border=0> + +<tr> +<td><code>$0</code></td> +<td> +<code>null</code>. +</td> +</tr> + +<tr> +<td><code>$1</code>, <code>$2</code>, ...    </td> +<td> +The parameters to the constructor. +</td> +</tr> + +<tr> +<td><code>$_</code></td> +<td rowspan=2> +The resulting value of the object creation. +<br>A newly created object must be stored in this variable. +</td> +</tr> + +<tr><td> </td></tr> + +<tr> +<td><code>$r</code></td> +<td> +The type of the created object. +</td> +</tr> + +<tr><td><code>$sig</code>    </td> +<td>An array of <code>java.lang.Class</code> objects representing +the formal parameter types.</td> +</tr> + +<tr><td><code>$type</code>    </td> +<td>A <code>java.lang.Class</code> object representing +the class of the created object. +</td></tr> + +<tr><td><code>$proceed</code>    </td> +<td>The name of a virtual method executing the original +object creation. +.</td> +</tr> + +</table> +</ul> + +<p>The other identifiers such as <code>$w</code>, +<code>$args</code> and <code>$$</code> +are also available. + +<h4>javassist.expr.NewArray</h4> + +<p>A <code>NewArray</code> object represents array creation +with the <code>new</code> operator. +The method <code>edit()</code> in <code>ExprEditor</code> +receives this object if array creation is found. +The method <code>replace()</code> in +<code>NewArray</code> receives +source text representing the substitued statement or +block for the array creation. + +<p> +In the source text, the identifiers starting with <code>$</code> +have special meaning: + +<ul><table border=0> + +<tr> +<td><code>$0</code></td> +<td> +<code>null</code>. +</td> +</tr> + +<tr> +<td><code>$1</code>, <code>$2</code>, ...    </td> +<td> +The size of each dimension. +</td> +</tr> + +<tr> +<td><code>$_</code></td> +<td rowspan=2> +The resulting value of the array creation. +<br>A newly created array must be stored in this variable. +</td> +</tr> + +<tr><td> </td></tr> + +<tr> +<td><code>$r</code></td> +<td> +The type of the created array. +</td> +</tr> + +<tr><td><code>$type</code>    </td> +<td>A <code>java.lang.Class</code> object representing +the class of the created array. +</td></tr> + +<tr><td><code>$proceed</code>    </td> +<td>The name of a virtual method executing the original +array creation. +.</td> +</tr> + +</table> +</ul> + +<p>The other identifiers such as <code>$w</code>, +<code>$args</code> and <code>$$</code> +are also available. + +<p>For example, if the array creation is the following expression, + +<ul><pre> +String[][] s = new String[3][4]; +</pre></ul> + +then the value of $1 and $2 are 3 and 4, respectively. $3 is not available. + +<p>If the array creation is the following expression, + +<ul><pre> +String[][] s = new String[3][]; +</pre></ul> + +then the value of $1 is 3 but $2 is not available. + +<h4>javassist.expr.Instanceof</h4> + +<p>A <code>Instanceof</code> object represents an <code>instanceof</code> +expression. +The method <code>edit()</code> in <code>ExprEditor</code> +receives this object if an instanceof expression is found. +The method <code>replace()</code> in +<code>Instanceof</code> receives +source text representing the substitued statement or +block for the expression. + +<p> +In the source text, the identifiers starting with <code>$</code> +have special meaning: + +<ul><table border=0> + +<tr> +<td><code>$0</code></td> +<td> +<code>null</code>. +</td> +</tr> + +<tr> +<td><code>$1</code></td> +<td> +The value on the left hand side of the original +<code>instanceof</code> operator. +</td> +</tr> + +<tr> +<td><code>$_</code></td> +<td> +The resulting value of the expression. +The type of <code>$_</code> is <code>boolean</code>. +</td> +</tr> + +<tr> +<td><code>$r</code></td> +<td> +The type on the right hand side of the <code>instanceof</code> operator. +</td> +</tr> + +<tr><td><code>$type</code></td> +<td>A <code>java.lang.Class</code> object representing +the type on the right hand side of the <code>instanceof</code> operator. +</td> +</tr> + +<tr><td><code>$proceed</code>    </td> +<td rowspan=4>The name of a virtual method executing the original +<code>instanceof</code> expression. +<br>It takes one parameter (the type is <code>java.lang.Object</code>) +and returns true +<br>if the parameter value is an instance of the type on the right +hand side of +<br>the original <code>instanceof</code> operator. +Otherwise, it returns false. +</td> +</tr> + +<tr><td> </td></tr> +<tr><td> </td></tr> +<tr><td> </td></tr> + +</table> +</ul> + +<p>The other identifiers such as <code>$w</code>, +<code>$args</code> and <code>$$</code> +are also available. + +<h4>javassist.expr.Cast</h4> + +<p>A <code>Cast</code> object represents an expression for +explicit type casting. +The method <code>edit()</code> in <code>ExprEditor</code> +receives this object if explicit type casting is found. +The method <code>replace()</code> in +<code>Cast</code> receives +source text representing the substitued statement or +block for the expression. + +<p> +In the source text, the identifiers starting with <code>$</code> +have special meaning: + +<ul><table border=0> + +<tr> +<td><code>$0</code></td> +<td> +<code>null</code>. +</td> +</tr> + +<tr> +<td><code>$1</code></td> +<td> +The value the type of which is explicitly cast. +</td> +</tr> + +<tr> +<td><code>$_</code></td> +<td rowspan=2> +The resulting value of the expression. +The type of <code>$_</code> is the same as the type +<br>after the explicit casting, that is, the type surrounded +by <code>( )</code>. +</td> +</tr> + +<tr><td> </td></tr> + +<tr> +<td><code>$r</code></td> +<td>the type after the explicit casting, or the type surrounded +by <code>( )</code>. +</td> +</tr> + +<tr><td><code>$type</code></td> +<td>A <code>java.lang.Class</code> object representing +the same type as <code>$r</code>. +</td> +</tr> + +<tr><td><code>$proceed</code>    </td> +<td rowspan=3>The name of a virtual method executing the original +type casting. +<br>It takes one parameter of the type <code>java.lang.Object</code> +and returns it after +<br>the explicit type casting specified by the original expression. + +</td> +</tr> + +<tr><td> </td></tr> + +<tr><td> </td></tr> + +</table> +</ul> + +<p>The other identifiers such as <code>$w</code>, +<code>$args</code> and <code>$$</code> +are also available. + +<h4>javassist.expr.Handler</h4> + +<p>A <code>Handler</code> object represents a <code>catch</code> +clause of <code>try-catch</code> statement. +The method <code>edit()</code> in <code>ExprEditor</code> +receives this object if a <code>catch</code> is found. +The method <code>insertBefore()</code> in +<code>Handler</code> compiles the received +source text and inserts it at the beginning of the <code>catch</code> clause. + +<p> +In the source text, the identifiers starting with <code>$</code> +have meaning: + +<ul><table border=0> + +<tr> +<td><code>$1</code></td> +<td> +The exception object caught by the <code>catch</code> clause. +</td> +</tr> + +<tr> +<td><code>$r</code></td> +<td>the type of the exception caught by the <code>catch</code> clause. +It is used in a cast expression. +</td> +</tr> + +<tr> +<td><code>$w</code></td> +<td>The wrapper type. It is used in a cast expression. +</td> +</tr> + +<tr><td><code>$type</code>    </td> +<td rowspan=2> +A <code>java.lang.Class</code> object representing +<br>the type of the exception caught by the <code>catch</code> clause. +</td> +</tr> + +<tr><td> </td></tr> + +</table> +</ul> + +<p>If a new exception object is assigned to <code>$1</code>, +it is passed to the original <code>catch</code> clause as the caught +exception. + +<p><br> + +<a name="add"> +<h3>4.3 Adding a new method or field</h3> + +<h4>Adding a method</h4> + +<p>Javassist allows the users to create a new method and constructor +from scratch. <code>CtNewMethod</code> +and <code>CtNewConstructor</code> provide several factory methods, +which are static methods for creating <code>CtMethod</code> or +<code>CtConstructor</code> objects. +Especially, <code>make()</code> creates +a <code>CtMethod</code> or <code>CtConstructor</code> object +from the given source text. + +<p>For example, this program: + +<ul><pre> +CtClass point = ClassPool.getDefault().get("Point"); +CtMethod m = CtNewMethod.make( + "public int xmove(int dx) { x += dx; }", + point); +point.addMethod(m); +</pre></ul> + +<p>adds a public method <code>xmove()</code> to class <code>Point</code>. +In this example, <code>x</code> is a <code>int</code> field in +the class <code>Point</code>. + +<p>The source text passed to <code>make()</code> can include the +identifiers starting with <code>$</code> except <code>$_</code> +as in <code>setBody()</code>. +It can also include +<code>$proceed</code> if the target object and the target method name +are also given to <code>make()</code>. For example, + +<ul><pre> +CtClass point = ClassPool.getDefault().get("Point"); +CtMethod m = CtNewMethod.make( + "public int ymove(int dy) { $proceed(0, dy); }", + point, "this", "move"); +</pre></ul> + +<p>this program creates a method <code>ymove()</code> defined below: + +<ul><pre> +public int ymove(int dy) { this.move(0, dy); } +</pre></ul> + +<p>Note that <code>$proceed</code> has been replaced with +<code>this.move</code>. + +<p>Javassist provides another way to add a new method. +You can first create an abstract method and later give it a method body: + +<ul><pre> +CtClass cc = ... ; +CtMethod m = new CtMethod(CtClass.intType, "move", + new CtClass[] { CtClass.intType }, cc); +cc.addMethod(m); +m.setBody("{ x += $1; }"); +cc.setModifiers(cc.getModifiers() & ~Modifier.ABSTRACT); +</pre></ul> + +<p>Since Javassist makes a class abstract if an abstract method is +added to the class, you have to explicitly change the class back to a +non-abstract one after calling <code>setBody()</code>. + + +<h4>Mutual recursive methods</h4> + +<p>Javassist cannot compile a method if it calls another method that +has not been added to a class. (Javassist can compile a method that +calls itself recursively.) To add mutual recursive methods to a class, +you need a trick shown below. Suppose that you want to add methods +<code>m()</code> and <code>n()</code> to a class represented +by <code>cc</code>: + +<ul><pre> +CtClass cc = ... ; +CtMethod m = CtNewMethod.make("public abstract int m(int i);", cc); +CtMethod n = CtNewMethod.make("public abstract int n(int i);", cc); +cc.addMethod(m); +cc.addMethod(n); +m.setBody("{ return ($1 <= 0) ? 1 : (n($1 - 1) * $1); }"); +n.setBody("{ return m($1); }"); +cc.setModifiers(cc.getModifiers() & ~Modifier.ABSTRACT); +</pre></ul> + +<p>You must first make two abstract methods and add them to the class. +Then you can give the method bodies to these methods even if the method +bodies include method calls to each other. Finally you must change the +class to a not-abstract class since <code>addMethod()</code> automatically +changes a class into an abstract one if an abstract method is added. + +<h4>Adding a field</h4> + +<p>Javassist also allows the users to create a new field. + +<ul><pre> +CtClass point = ClassPool.getDefault().get("Point"); +CtField f = new CtField(CtClass.intType, "z", point); +point.addField(f); +</pre></ul> + +<p>This program adds a field named <code>z</code> to class +<code>Point</code>. + +<p>If the initial value of the added field must be specified, +the program shown above must be modified into: + +<ul><pre> +CtClass point = ClassPool.getDefault().get("Point"); +CtField f = new CtField(CtClass.intType, "z", point); +point.addField(f, "0"); <em>// initial value is 0.</em> +</pre></ul> + +<p>Now, the method <code>addField()</code> receives the second parameter, +which is the source text representing an expression computing the initial +value. This source text can be any Java expression if the result type +of the expression matches the type of the field. Note that an expression +does not end with a semi colon (<code>;</code>). + +<p>Furthermore, the above code can be rewritten into the following +simple code: + +<ul><pre> +CtClass point = ClassPool.getDefault().get("Point"); +CtField f = CtField.make("public int z = 0;", point); +point.addField(f); +</pre></ul> + +<h4>Removing a member</h4> + +<p>To remove a field or a method, call <code>removeField()</code> +or <code>removeMethod()</code> in <code>CtClass</code>. A +<code>CtConstructor</code> can be removed by <code>removeConstructor()</code> +in <code>CtClass</code>. + +<p><br> + +<a name="annotation"> +<h3>4.4 Annotations</h3> + +<p><code>CtClass</code>, <code>CtMethod</code>, <code>CtField</code> +and <code>CtConstructor</code> provides a convenient method +<code>getAnnotations()</code> for reading annotations. +It returns an annotation-type object. + +<p>For example, suppose the following annotation: + +<ul><pre> +public @interface Author { + String name(); + int year(); +} +</pre></ul> + +<p>This annotation is used as the following: + +<ul><pre> +@Author(name="Chiba", year=2005) +public class Point { + int x, y; +} +</pre></ul> + +<p>Then, the value of the annotation can be obtained by +<code>getAnnotations()</code>. +It returns an array containing +annotation-type objects. + +<ul><pre> +CtClass cc = ClassPool.getDefault().get("Point"); +Object[] all = cc.getAnnotations(); +Author a = (Author)all[0]; +String name = a.name(); +int year = a.year(); +System.out.println("name: " + name + ", year: " + year); +</pre></ul> + +<p>This code snippet should print: + +<ul><pre> +name: Chiba, year: 2005 +</pre></ul> + +<p> +Since the annoation of <code>Point</code> is only <code>@Author</code>, +the length of the array <code>all</code> is one +and <code>all[0]</code> is an <code>Author</code> object. +The member values of the annotation can be obtained +by calling <code>name()</code> and <code>year()</code> +on the <code>Author</code> object. + +<p>To use <code>getAnnotations()</code>, annotation types +such as <code>Author</code> must be included in the current +class path. <em>They must be also accessible from a +<code>ClassPool</code> object.</em> If the class file of an annotation +type is not found, Javassist cannot obtain the default values +of the members of that annotation type. + +<p><br> + +<a name="runtime"> +<h3>4.5 Runtime support classes</h3> + +<p>In most cases, a class modified by Javassist does not require +Javassist to run. However, some kinds of bytecode generated by the +Javassist compiler need runtime support classes, which are in the +<code>javassist.runtime</code> package (for details, please read +the API reference of that package). Note that the +<code>javassist.runtime</code> package is the only package that +classes modified by Javassist may need for running. The other +Javassist classes are never used at runtime of the modified classes. + +<p><br> + +<a name="import"> +<h3>4.6 Import</h3> + +<p>All the class names in source code must be fully qualified +(they must include package names). +However, the <code>java.lang</code> package is an +exception; for example, the Javassist compiler can +resolve <code>Object</code> as +well as <code>java.lang.Object</code>. + +<p>To tell the compiler to search other packages when resolving a +class name, call <code>importPackage()</code> in <code>ClassPool</code>. +For example, + +<ul><pre> +ClassPool pool = ClassPool.getDefault(); +pool.importPackage("java.awt"); +CtClass cc = pool.makeClass("Test"); +CtField f = CtField.make("public Point p;", cc); +cc.addField(f); +</pre></ul> + +<p>The seconde line instructs the compiler +to import the <code>java.awt</code> package. +Thus, the third line will not throw an exception. +The compiler can recognize <code>Point</code> +as <code>java.awt.Point</code>. + +<p>Note that <code>importPackage()</code> <em>does not</em> affect +the <code>get()</code> method in <code>ClassPool</code>. +Only the compiler considers the imported packages. +The parameter to <code>get()</code> +must be always a fully qualified name. + +<p><br> + +<a name="limit"> +<h3>4.7 Limitations</h3> + +<p>In the current implementation, the Java compiler included in Javassist +has several limitations with respect to the language that the compiler can +accept. Those limitations are: + +<p><li>The new syntax introduced by J2SE 5.0 (including enums and generics) +has not been supported. Annotations are supported only by the low level +API of Javassist. +See the <code>javassist.bytecode.annotation</code> package. + +<p><li>Array initializers, a comma-separated list of expressions +enclosed by braces <code>{</code> and <code>}</code>, are not +available unless the array dimension is one. + +<p><li>Inner classes or anonymous classes are not supported. + +<p><li>Labeled <code>continue</code> and <code>break</code> statements +are not supported. + +<p><li>The compiler does not correctly implement the Java method dispatch +algorithm. The compiler may confuse if methods defined in a class +have the same name but take different parameter lists. + +<p>For example, + +<ul><pre> +class A {} +class B extends A {} +class C extends B {} + +class X { + void foo(A a) { .. } + void foo(B b) { .. } +} +</pre></ul> + +<p>If the compiled expression is <code>x.foo(new C())</code>, where +<code>x</code> is an instance of X, the compiler may produce a call +to <code>foo(A)</code> although the compiler can correctly compile +<code>foo((B)new C())</code>. + +<p><li>The users are recommended to use <code>#</code> as the separator +between a class name and a static method or field name. +For example, in regular Java, + +<ul><pre>javassist.CtClass.intType.getName()</pre></ul> + +<p>calls a method <code>getName()</code> on +the object indicated by the static field <code>intType</code> +in <code>javassist.CtClass</code>. In Javassist, the users can +write the expression shown above but they are recommended to +write: + +<ul><pre>javassist.CtClass#intType.getName()</pre></ul> + +<p>so that the compiler can quickly parse the expression. +</ul> + +<p><br> + +<a href="tutorial.html">Previous page</a> + <a href="tutorial3.html">Next page</a> + +<hr> +Java(TM) is a trademark of Sun Microsystems, Inc.<br> +Copyright (C) 2000-2010 by Shigeru Chiba, All rights reserved. +</body> +</html> diff --git a/tutorial/tutorial3.html b/tutorial/tutorial3.html new file mode 100644 index 0000000..dd4fb79 --- /dev/null +++ b/tutorial/tutorial3.html @@ -0,0 +1,366 @@ +<html> +<head> + <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> + <title>Javassist Tutorial</title> + <link rel="stylesheet" type="text/css" href="brown.css"> +</head> + +<body> + +<div align="right">Getting Started with Javassist</div> + +<div align="left"><a href="tutorial2.html">Previous page</a></div> + +<p> +<a href="#intro">5. Bytecode level API</a> +<ul> +<li><a href="#classfile">Obtaining a <code>ClassFile</code> object</a> +<br><li><a href="#member">Adding and removing a member</a> +<br><li><a href="#traverse">Traversing a method body</a> +<br><li><a href="#bytecode">Producing a bytecode sequence</a> +<br><li><a href="#annotation">Annotations (Meta tags)</a> + +</ul> + +<p><a href="#generics">6. Generics</a> + +<p><a href="#varargs">7. Varargs</a> + +<p><a href="#j2me">8. J2ME</a> + +<p><br> + +<a name="intro"> +<h2>5. Bytecode level API</h2> + +<p> +Javassist also provides lower-level API for directly editing +a class file. To use this level of API, you need detailed +knowledge of the Java bytecode and the class file format +while this level of API allows you any kind of modification +of class files. + +<p> +If you want to just produce a simple class file, +<code>javassist.bytecode.ClassFileWriter</code> might provide +the best API for you. It is much faster than +<code>javassist.bytecode.ClassFile</code> although its API +is minimum. + +<a name="classfile"> +<h3>5.1 Obtaining a <code>ClassFile</code> object</h3> + +<p>A <code>javassist.bytecode.ClassFile</code> object represents +a class file. To obtian this object, <code>getClassFile()</code> +in <code>CtClass</code> should be called. + +<p>Otherwise, you can construct a +<code>javassist.bytecode.ClassFile</code> directly from a class file. +For example, + +<ul><pre> +BufferedInputStream fin + = new BufferedInputStream(new FileInputStream("Point.class")); +ClassFile cf = new ClassFile(new DataInputStream(fin)); +</pre></ul> + +<p> +This code snippet creats a <code>ClassFile</code> object from +<code>Point.class</code>. + +<p> +A <code>ClassFile</code> object can be written back to a +class file. <code>write()</code> in <code>ClassFile</code> +writes the contents of the class file to a given +<code>DataOutputStream</code>. + +<p><br> + +<a name="member"> +<h3>5.2 Adding and removing a member</h3> + +<p> +<code>ClassFile</code> provides <code>addField()</code> and +<code>addMethod()</code> for adding a field or a method (note that +a constructor is regarded as a method at the bytecode level). +It also provides <code>addAttribute()</code> for adding an attribute +to the class file. + +<p> +Note that <code>FieldInfo</code>, <code>MethodInfo</code>, and +<code>AttributeInfo</code> objects include a link to a +<code>ConstPool</code> (constant pool table) object. The <code>ConstPool</code> +object must be common to the <code>ClassFile</code> object and +a <code>FieldInfo</code> (or <code>MethodInfo</code> etc.) object +that is added to that <code>ClassFile</code> object. +In other words, a <code>FieldInfo</code> (or <code>MethodInfo</code> etc.) object +must not be shared among different <code>ClassFile</code> objects. + +<p> +To remove a field or a method from a <code>ClassFile</code> object, +you must first obtain a <code>java.util.List</code> +object containing all the fields of the class. <code>getFields()</code> +and <code>getMethods()</code> return the lists. A field or a method can +be removed by calling <code>remove()</code> on the <code>List</code> object. +An attribute can be removed in a similar way. +Call <code>getAttributes()</code> in <code>FieldInfo</code> or +<code>MethodInfo</code> to obtain the list of attributes, +and remove one from the list. + + +<p><br> + +<a name="traverse"> +<h3>5.3 Traversing a method body</h3> + +<p> +To examine every bytecode instruction in a method body, +<code>CodeIterator</code> is useful. To otbain this object, +do as follows: + +<ul><pre> +ClassFile cf = ... ; +MethodInfo minfo = cf.getMethod("move"); // we assume move is not overloaded. +CodeAttribute ca = minfo.getCodeAttribute(); +CodeIterator i = ca.iterator(); +</pre></ul> + +<p> +A <code>CodeIterator</code> object allows you to visit every +bytecode instruction one by one from the beginning to the end. +The following methods are part of the methods declared in +<code>CodeIterator</code>: + +<ul> +<li><code>void begin()</code><br> +Move to the first instruction.<br> +<li><code>void move(int index)</code><br> +Move to the instruction specified by the given index.<br> +<li><code>boolean hasNext()</code><br> +Returns true if there is more instructions.<br> +<li><code>int next()</code><br> +Returns the index of the next instruction.<br> +<em>Note that it does not return the opcode of the next +instruction.</em><br> +<li><code>int byteAt(int index)</code><br> +Returns the unsigned 8bit value at the index.<br> +<li><code>int u16bitAt(int index)</code><br> +Returns the unsigned 16bit value at the index.<br> +<li><code>int write(byte[] code, int index)</code><br> +Writes a byte array at the index.<br> +<li><code>void insert(int index, byte[] code)</code><br> +Inserts a byte array at the index. +Branch offsets etc. are automatically adjusted.<br> +</ul> + +<p>The following code snippet displays all the instructions included +in a method body: + +<ul><pre> +CodeIterator ci = ... ; +while (ci.hasNext()) { + int index = ci.next(); + int op = ci.byteAt(index); + System.out.println(Mnemonic.OPCODE[op]); +} +</pre></ul> + +<p><br> + +<a name="bytecode"> +<h3>5.4 Producing a bytecode sequence</h3> + +<p> +A <code>Bytecode</code> object represents a sequence of bytecode +instructions. It is a growable array of bytecode. +Here is a sample code snippet: + +<ul><pre> +ConstPool cp = ...; // constant pool table +Bytecode b = new Bytecode(cp, 1, 0); +b.addIconst(3); +b.addReturn(CtClass.intType); +CodeAttribute ca = b.toCodeAttribute(); +</pre></ul> + +<p> +This produces the code attribute representing the following sequence: + +<ul><pre> +iconst_3 +ireturn +</pre></ul> + +<p> +You can also obtain a byte array containing this sequence by +calling <code>get()</code> in <code>Bytecode</code>. The +obtained array can be inserted in another code attribute. + +<p> +While <code>Bytecode</code> provides a number of methods for adding a +specific instruction to the sequence, it provides +<code>addOpcode()</code> for adding an 8bit opcode and +<code>addIndex()</code> for adding an index. +The 8bit value of each opcode is defined in the <code>Opcode</code> +interface. + +<p> +<code>addOpcode()</code> and other methods for adding a specific +instruction are automatically maintain the maximum stack depth +unless the control flow does not include a branch. +This value can be obtained by calling <code>getMaxStack()</code> +on the <code>Bytecode</code> object. +It is also reflected on the <code>CodeAttribute</code> object +constructed from the <code>Bytecode</code> object. +To recompute the maximum stack depth of a method body, +call <code>computeMaxStack()</code> in <code>CodeAttribute</code>. + +<p><br> + +<a name="annotation"> +<h3>5.5 Annotations (Meta tags)</h3> + +<p>Annotations are stored in a class file +as runtime invisible (or visible) annotations attribute. +These attributes can be obtained from <code>ClassFile</code>, +<code>MethodInfo</code>, or <code>FieldInfo</code> objects. +Call <code>getAttribute(AnnotationsAttribute.invisibleTag)</code> +on those objects. For more details, see the javadoc manual +of <code>javassist.bytecode.AnnotationsAttribute</code> class +and the <code>javassist.bytecode.annotation</code> package. + +<p>Javassist also let you access annotations by the higher-level +API. +If you want to access annotations through <code>CtClass</code>, +call <code>getAnnotations()</code> in <code>CtClass</code> or +<code>CtBehavior</code>. + +<p><br> + +<h2><a name="generics">6. Generics</a></h2> + +<p>The lower-level API of Javassist fully supports generics +introduced by Java 5. On the other hand, the higher-level +API such as <code>CtClass</code> does not directly support +generics. However, this is not a serious problem for bytecode +transformation. + +<p>The generics of Java is implemented by the erasure technique. +After compilation, all type parameters are dropped off. For +example, suppose that your source code declares a parameterized +type <code>Vector<String></code>: + +<ul><pre> +Vector<String> v = new Vector<String>(); + : +String s = v.get(0); +</pre></ul> + +<p>The compiled bytecode is equivalent to the following code: + +<ul><pre> +Vector v = new Vector(); + : +String s = (String)v.get(0); +</pre></ul> + +<p>So when you write a bytecode transformer, you can just drop +off all type parameters. For example, if you have a class: + +<ul><pre> +public class Wrapper<T> { + T value; + public Wrapper(T t) { value = t; } +} +</pre></ul> + +<p>and want to add an interface <code>Getter<T></code> to the +class <code>Wrapper<T></code>: + +<ul><pre> +public interface Getter<T> { + T get(); +} +</pre></ul> + +<p>Then the interface you really have to add is <code>Getter</code> +(the type parameters <code><T></code> drops off) +and the method you also have to add to the <code>Wrapper</code> +class is this simple one: + +<ul><pre> +public Object get() { return value; } +</pre></ul> + +<p>Note that no type parameters are necessary. + +<p><br> + +<h2><a name="varargs">7. Varargs</a></h2> + +<p>Currently, Javassist does not directly support varargs. So to make a method with varargs, +you must explicitly set a method modifier. But this is easy. +Suppose that now you want to make the following method: + +<ul><pre> +public int length(int... args) { return args.length; } +</pre></ul> + +<p>The following code using Javassist will make the method shown above: + +<ul><pre> +CtClass cc = /* target class */; +CtMethod m = CtMethod.make("public int length(int[] args) { return args.length; }", cc); +m.setModifiers(m.getModifiers() | Modifier.VARARGS); +cc.addMethod(m); +<pre></ul> + +<p>The parameter type <code>int...</code> is changed into <code>int[]</code> +and <code>Modifier.VARARGS</code> is added to the method modifiers. + +<p>To call this method, you must write: + +<ul><pre> +length(new int[] { 1, 2, 3 }); +</pre></ul> + +<p>instead of this method call using the varargs mechanism: + +<ul><pre> +length(1, 2, 3); +</pre></ul> + +<p><br> + +<h2><a name="j2me">8. J2ME</a></h2> + +<p>If you modify a class file for the J2ME execution environment, +you must perform <it>preverification</it>. Preverifying is basically +producing stack maps, which is similar to stack map tables introduced +into J2SE at JDK 1.6. Javassist maintains the stack maps for J2ME only if +<code>javassist.bytecode.MethodInfo.doPreverify</code> is true. + +<p>You can also manually +produce a stack map for a modified method. +For a given method represented by a <code>CtMethod</code> object <code>m</code>, +you can produce a stack map by calling the following methods: + +<ul><pre> +m.getMethodInfo().rebuildStackMapForME(cpool); +</pre></ul> + +<p>Here, <code>cpool</code> is a <code>ClassPool</code> object, which is +available by calling <code>getClassPool()</code> on a <code>CtClass</code> +object. A <code>ClassPool</code> object is responsible for finding +class files from given class pathes. To obtain all the <code>CtMethod</code> +objects, call the <code>getDeclaredMethods</code> method on a <code>CtClass</code> object. + +<p><br> + +<a href="tutorial2.html">Previous page</a> + +<hr> +Java(TM) is a trademark of Sun Microsystems, Inc.<br> +Copyright (C) 2000-2010 by Shigeru Chiba, All rights reserved. +</body> +</html> |