1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
|
/**
* Copyright 2012-2014 Julien Eluard and contributors
* This project includes software developed by Julien Eluard: https://github.com/jeluard/
*
* 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.
*/
package org.osjava.jardiff;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import org.objectweb.asm.Opcodes;
/**
* A set of Tools which do not belong anywhere else in the API at this time.
* This is nasty, but for now, useful.
*
* @author <a href="mailto:antony@cyberiantiger.org">Antony Riley</a>
*/
public final class Tools
{
/**
* Private constructor so this class can't be instantiated.
*/
private Tools() {
/* empty */
}
/**
* Returns {@code true} if description has changed, i.e. the
* {@link MethodInfo#getDesc()} describing the return value.
* @param oldDesc
* @param newDesc
*/
public static boolean isDescChange(final String oldDesc, final String newDesc) {
return null == oldDesc && null != newDesc ||
null != oldDesc && !oldDesc.equals(newDesc);
}
/**
* Get the java class name given an internal class name.
* This method currently replaces all instances of $ and / with . this
* may not be according to the java language spec, and will almost
* certainly fail for some inner classes.
*
* @param internalName The internal name of the class.
* @return The java class name.
*/
public static final String getClassName(final String internalName) {
final StringBuffer ret = new StringBuffer(internalName.length());
for (int i = 0; i < internalName.length(); i++) {
final char ch = internalName.charAt(i);
switch (ch) {
case '$':
case '/':
ret.append('.');
break;
default:
ret.append(ch);
}
}
return ret.toString();
}
private static boolean has(final int value, final int mask) {
return (value & mask) != 0;
}
private static boolean not(final int value, final int mask) {
return (value & mask) == 0;
}
private static boolean isAccessIncompatible(final int oldAccess, final int newAccess) {
if (has(newAccess, Opcodes.ACC_PUBLIC)) {
return false;
} else if (has(newAccess, Opcodes.ACC_PROTECTED)) {
return has(oldAccess, Opcodes.ACC_PUBLIC);
} else if (has(newAccess, Opcodes.ACC_PRIVATE)) {
return not(oldAccess, Opcodes.ACC_PRIVATE);
} else {
// new access is package, it is incompatible if old access was public or protected
return has(oldAccess, Opcodes.ACC_PUBLIC | Opcodes.ACC_PROTECTED);
}
}
/**
* @deprecated Use {@link #isClassAccessChange(int, int)}.
*/
public static boolean isAccessChange(final int oldAccess, final int newAccess) {
return isClassAccessChange(oldAccess, newAccess);
}
/**
* Returns whether a class's newAccess is incompatible with oldAccess
* following <a href="http://docs.oracle.com/javase/specs/jls/se7/html/jls-13.html">Java Language Specification, Java SE 7 Edition</a>:
* <ul>
* <li><a href="http://docs.oracle.com/javase/specs/jls/se7/html/jls-13.html#jls-13.4.1">13.4.1 abstract Classes</a><ul>
* <li>If a class that was not declared abstract is changed to be declared abstract,
* then pre-existing binaries that attempt to create new instances of that class
* will throw either an InstantiationError at link time,
* or (if a reflective method is used) an InstantiationException at run time.
* Such changes <b>break backward compatibility</b>!</li>
* <li>Changing a class that is declared abstract to no longer be declared abstract
* <b>does not break compatibility</b> with pre-existing binaries.</li>
* </ul></li>
* <li><a href="http://docs.oracle.com/javase/specs/jls/se7/html/jls-13.html#jls-13.4.2">13.4.2 final Classes</a><ul>
* <li>If a class that was not declared final is changed to be declared final,
* then a VerifyError is thrown if a binary of a pre-existing subclass of this class is loaded,
* because final classes can have no subclasses.
* Such changes <b>break functional backward compatibility</b>!</li>
* <li>Changing a class that is declared final to no longer be declared final
* <b>does not break compatibility</b> with pre-existing binaries.</li>
* </ul></li>
* </ul>
*
* @param oldAccess
* @param newAccess
* @return
*/
public static boolean isClassAccessChange(final int oldAccess, final int newAccess) {
if ( not(oldAccess, Opcodes.ACC_ABSTRACT) && has(newAccess, Opcodes.ACC_ABSTRACT) ) {
return true; // 13.4.1 #1
} else if ( not(oldAccess, Opcodes.ACC_FINAL) && has(newAccess, Opcodes.ACC_FINAL) ) {
return true; // 13.4.2 #1
} else {
final int compatibleChanges = Opcodes.ACC_ABSTRACT | // 13.4.1 #2
Opcodes.ACC_FINAL ; // 13.4.2 #2
// FIXME Opcodes.ACC_VOLATILE ?
final int oldAccess2 = oldAccess & ~compatibleChanges;
final int newAccess2 = newAccess & ~compatibleChanges;
return oldAccess2 != newAccess2;
}
}
/**
* Returns whether a field's newAccess is incompatible with oldAccess
* following <a href="http://docs.oracle.com/javase/specs/jls/se7/html/jls-13.html">Java Language Specification, Java SE 7 Edition</a>:
* <ul>
* <li><a href="http://docs.oracle.com/javase/specs/jls/se7/html/jls-13.html#jls-13.4.9">13.4.7 Access to Members and Constructors</a><ul>
* <li>Changing the declared access of a member or constructor to permit less access
* <b>may break compatibility</b> with pre-existing binaries, causing a linkage error to be thrown when these binaries are resolved.
* </li>
* <li>The binary format is defined so that changing a member or constructor to be more accessible does not cause a
* linkage error when a subclass (already) defines a method to have less access.
* </li>
* </ul></li>
* <li><a href="http://docs.oracle.com/javase/specs/jls/se7/html/jls-13.html#jls-13.4.9">13.4.9 final Fields and Constants</a><ul>
* <li>If a field that was not declared final is changed to be declared final,
* then it <b>can break compatibility</b> with pre-existing binaries that attempt to assign new values to the field.</li>
* <li>Deleting the keyword final or changing the value to which a <i>non-final</i> field is initialized
* <b>does not break compatibility</b> with existing binaries.</li>
* <li>If a field is a constant variable (§4.12.4),
* then deleting the keyword final or changing its value
* will <i>not break compatibility</i> with pre-existing binaries by causing them not to run,
* but they will not see any new value for the usage of the field unless they are recompiled.
* This is true even if the usage itself is not a compile-time constant expression (§15.28).
* Such changes <b>break functional backward compatibility</b>!</li>
* </ul></li>
* <li><a href="http://docs.oracle.com/javase/specs/jls/se7/html/jls-13.html#jls-13.4.10">13.4.10 static Fields</a><ul>
* <li>If a field that is not declared private was not declared static
* and is changed to be declared static, or vice versa,
* then a linkage error, specifically an IncompatibleClassChangeError,
* will result if the field is used by a pre-existing binary which expected a field of the other kind.
* Such changes <b>break backward compatibility</b>!</li>
* </ul></li>
* <li><a href="http://docs.oracle.com/javase/specs/jls/se7/html/jls-13.html#jls-13.4.11">13.4.11. transient Fields </a><ul>
* <li>Adding or deleting a transient modifier of a field
* <b>does not break compatibility</b> with pre-existing binaries.</li>
* </ul></li>
* <li><a href="http://www.wsu.edu/UNIX_Systems/java/langspec-1.0/13.doc.html#45194">13.4.11 volatile Fields (JLS 1.0)</a><ul>
* <li>If a field that is not declared private was not declared volatile
* and is changed to be declared volatile, or vice versa, then a linkage time error,
* specifically an IncompatibleClassChangeError, may result if the field is used
* by a preexisting binary that expected a field of the opposite volatility.
* Such changes <b>break backward compatibility</b>!</li>
* </ul></li>
* </ul>
*
* @param oldAccess
* @param newAccess
* @return
*/
public static boolean isFieldAccessChange(final int oldAccess, final int newAccess) {
if (isAccessIncompatible(oldAccess, newAccess)) {
return true; // 13.4.7
}
if ( not(oldAccess, Opcodes.ACC_FINAL) && has(newAccess, Opcodes.ACC_FINAL) ) {
return true; // 13.4.9 #1
} else {
final int compatibleChanges = Opcodes.ACC_FINAL | // 13.4.9 #2
Opcodes.ACC_TRANSIENT; // 13.4.11 #1
final int accessPermissions = Opcodes.ACC_PUBLIC |
Opcodes.ACC_PROTECTED |
Opcodes.ACC_PRIVATE;
final int oldAccess2 = oldAccess & ~compatibleChanges & ~accessPermissions;
final int newAccess2 = newAccess & ~compatibleChanges & ~accessPermissions;
return oldAccess2 != newAccess2;
}
}
/**
* Returns whether a method's newAccess is incompatible with oldAccess
* following <a href="http://docs.oracle.com/javase/specs/jls/se7/html/jls-13.html">Java Language Specification, Java SE 7 Edition</a>:
* <ul>
* <li><a href="http://docs.oracle.com/javase/specs/jls/se7/html/jls-13.html#jls-13.4.9">13.4.7 Access to Members and Constructors</a><ul>
* <li>Changing the declared access of a member or constructor to permit less access
* <b>may break compatibility</b> with pre-existing binaries, causing a linkage error to be thrown when these binaries are resolved.
* </li>
* <li>The binary format is defined so that changing a member or constructor to be more accessible does not cause a
* linkage error when a subclass (already) defines a method to have less access.
* </li>
* </ul></li>
* <li><a href="http://docs.oracle.com/javase/specs/jls/se7/html/jls-13.html#jls-13.4.16">13.4.16 abstract Methods</a><ul>
* <li>Changing a method that is declared abstract to no longer be declared abstract
* <b>does not break compatibility</b> with pre-existing binaries.</li>
* <li>Changing a method that is not declared abstract to be declared abstract
* <b>will break compatibility</b> with pre-existing binaries that previously invoked the method, causing an AbstractMethodError.</li>
* </ul></li>
* <li><a href="http://docs.oracle.com/javase/specs/jls/se7/html/jls-13.html#jls-13.4.17">13.4.17 final</a><ul>
* <li>Changing a method that is declared final to no longer be declared final
* <b>does not break compatibility</b> with pre-existing binaries.</li>
* <li>Changing an instance method that is not declared final to be declared final
* <b>may break compatibility</b> with existing binaries that depend on the ability to override the method.</li>
* <li>Changing a class (static) method that is not declared final to be declared final
* <b>does not break compatibility</b> with existing binaries, because the method could not have been overridden.</li>
* </ul></li>
* <li><a href="http://docs.oracle.com/javase/specs/jls/se7/html/jls-13.html#jls-13.4.18">13.4.18 native Methods</a><ul>
* <li>Adding or deleting a native modifier of a method
* <b>does not break compatibility</b> with pre-existing binaries.</li>
* </ul></li>
* <li><a href="http://docs.oracle.com/javase/specs/jls/se7/html/jls-13.html#jls-13.4.19">13.4.19 static Methods</a><ul>
* <li>If a method that is not declared private is also declared static (that is, a class method)
* and is changed to not be declared static (that is, to an instance method), or vice versa,
* then <i>compatibility with pre-existing binaries may be broken</i>, resulting in a linkage time error,
* namely an IncompatibleClassChangeError, if these methods are used by the pre-existing binaries.
* Such changes <b>break functional backward compatibility</b>!</li>
* </ul></li>
* <li><a href="http://docs.oracle.com/javase/specs/jls/se7/html/jls-13.html#jls-13.4.20">13.4.20 synchronized Methods</a><ul>
* <li>Adding or deleting a synchronized modifier of a method
* <b>does not break compatibility</b> with pre-existing binaries.</li>
* </ul></li>
* <li><a href="http://docs.oracle.com/javase/specs/jls/se7/html/jls-13.html#jls-13.4.21">13.4.21 Method and Constructor Throws</a><ul>
* <li>Changes to the throws clause of methods or constructors
* <b>do not break compatibility</b> with pre-existing binaries; these clauses are checked only at compile time.</li>
* </ul></li>
* </ul>
*
* @param oldAccess
* @param newAccess
* @return
*/
public static boolean isMethodAccessChange(final int oldAccess, final int newAccess) {
if (isAccessIncompatible(oldAccess, newAccess)) {
return true; // 13.4.7
}
if ( not(oldAccess, Opcodes.ACC_ABSTRACT) && has(newAccess, Opcodes.ACC_ABSTRACT) ) {
return true; // 13.4.16 #2
} else if ( not(oldAccess, Opcodes.ACC_FINAL) && not(oldAccess, Opcodes.ACC_STATIC) &&
has(newAccess, Opcodes.ACC_FINAL) ) {
return true; // 13.4.17 #2 excluding and #3
} else {
final int compatibleChanges = Opcodes.ACC_ABSTRACT | // 13.4.16 #1
Opcodes.ACC_FINAL | // 13.4.17 #1
Opcodes.ACC_NATIVE | // 13.4.18 #1
Opcodes.ACC_SYNCHRONIZED; // 13.4.20 #1
final int accessPermissions = Opcodes.ACC_PUBLIC |
Opcodes.ACC_PROTECTED |
Opcodes.ACC_PRIVATE;
final int oldAccess2 = oldAccess & ~compatibleChanges & ~accessPermissions;
final int newAccess2 = newAccess & ~compatibleChanges & ~accessPermissions;
return oldAccess2 != newAccess2;
}
}
/**
* Returns whether a method's oldThrows clause differs with newThrows.
* <p>
* Note that
* following <a href="http://docs.oracle.com/javase/specs/jls/se7/html/jls-13.html">Java Language Specification, Java SE 7 Edition</a>:
* <ul>
* <li><a href="https://docs.oracle.com/javase/specs/jls/se7/html/jls-13.html#jls-13.4.21">13.4.21 Method and Constructor Throws</a><ul>
* <li>Changes to the throws clause of methods or constructors
* <b>do not break compatibility with pre-existing binaries</b>;
* these clauses are <b>checked only at compile time</b>.
* </li>
* </ul></li>
* </ul>
* </p>
* @param oldThrows
* @param newThrows
*
* @return
*/
public static boolean isThrowsClauseChange(final String[] oldThrows, final String[] newThrows) {
if (oldThrows == null || newThrows == null) {
return oldThrows != newThrows;
} else {
final Set<String> oldExceptions = new HashSet<String>(Arrays.asList(oldThrows));
final Set<String> newExceptions = new HashSet<String>(Arrays.asList(newThrows));
return !oldExceptions.equals(newExceptions);
}
}
public static boolean isFieldTypeChange(final Object oldValue, final Object newValue) {
if (oldValue == null || newValue == null) {
return oldValue != newValue;
} else {
return !oldValue.getClass().equals(newValue.getClass());
}
}
/**
* Returns whether a field's oldValue differs with newValue.
* <p>
* Note that
* following <a href="http://docs.oracle.com/javase/specs/jls/se7/html/jls-13.html">Java Language Specification, Java SE 7 Edition</a>:
* <ul>
* <li><a href="https://docs.oracle.com/javase/specs/jls/se7/html/jls-13.html#jls-13.4.9">13.4.9 final Fields and Constants</a><ul>
* <li>Deleting the keyword final or changing the value to which a field is initialized
* <b>does not break compatibility</b> with existing binaries.
* </li>
* </ul></li>
* </ul>
* </p>
* @param oldValue
* @param newValue
* @return
*/
public static boolean isFieldValueChange(final Object oldValue, final Object newValue) {
if (oldValue == null || newValue == null) {
return oldValue != newValue;
} else {
return !oldValue.equals(newValue);
}
}
}
|