Download presentation
Presentation is loading. Please wait.
Published byTyler Mervyn Cooper Modified over 6 years ago
1
Program Sliding Ran Ettinger, IBM Research – Haifa
Advanced Software Tools Seminar Tel Aviv University 28 May 2012
2
Programmers use slices (not only) when debugging
Program slicing was invented, by Mark Weiser, for “times when only a portion of a program’s behavior is of interest” [ICSE81] and with the observation that “programmers use slices when debugging” [CACM82] According to Weiser: slicing is a “method of program decomposition” that “is applied to programs after they are written, and is therefore useful in maintenance rather than design” [ICSE81] Is this still true? What about re-design, refactoring, for supporting incremental and iterative development methodologies?
3
Program Sliding A slice-motion transformation
Isolates a slice, making it contiguous Identify the complementary slice (co-slice) A sequential (re-)composition of the slice and co-slice Preserves semantics for all variables
4
Some sliding examples…
5
Sliding {def}
6
int i, ceil; for (i = 0, ceil = this.extra[0].length > 3 ? 3 : this.extra[0].length; i < ceil; i++) { def += "," + this.extra[0][i]; //$NON-NLS-1$ } for (i = 0, ceil = this.extra[0].length > 3 ? 3 : this.extra[0].length; i < ceil; i++) { pot += "," + this.extra[1][i]; //$NON-NLS-1$ }
7
int i, ceil; for (i = 0, ceil = this.extra[0].length > 3 ? 3 : this.extra[0].length; i < ceil; i++) { def += "," + this.extra[0][i]; //$NON-NLS-1$ } for (i = 0, ceil = this.extra[0].length > 3 ? 3 : this.extra[0].length; i < ceil; i++) { pot += "," + this.extra[1][i]; //$NON-NLS-1$ }
8
Again, sliding {def}
9
String pot = "], pot:[" + this.potentialInits; //$NON-NLS-1$
for (i = 0, ceil = this.extra[0].length > 3 ? 3 : this.extra[0].length; i < ceil; i++) { pot += "," + this.extra[1][i]; //$NON-NLS-1$ } if (ceil < this.extra[0].length) { pot += ",..."; //$NON-NLS-1$ return def + pot + "], reachable:" + ((this.tagBits & UNREACHABLE) == 0) //$NON-NLS-1$ + ", no null info>"; //$NON-NLS-1$ String def = "FlowInfo<def:[" + this.definiteInits; //$NON-NLS-1$ int i, ceil; for (i = 0, ceil = this.extra[0].length > 3 ? 3 : this.extra[0].length; i < ceil; i++) { def += "," + this.extra[0][i]; //$NON-NLS-1$ } if (ceil < this.extra[0].length) { def += ",..."; //$NON-NLS-1$ tucking would do the same (if we focus on the tightest SESE, the one without the return statement) KH03 would keep the loop as is, and would move the last assignment to pot to the after part
10
String def = "FlowInfo<def:[" + this.definiteInits; //$NON-NLS-1$
int i, ceil; for (i = 0, ceil = this.extra[0].length > 3 ? 3 : this.extra[0].length; i < ceil; i++) { def += "," + this.extra[0][i]; //$NON-NLS-1$ } if (ceil < this.extra[0].length) { def += ",..."; //$NON-NLS-1$ String pot = "], pot:[" + this.potentialInits; //$NON-NLS-1$ for (i = 0, ceil = this.extra[0].length > 3 ? 3 : this.extra[0].length; i < ceil; i++) { pot += "," + this.extra[1][i]; //$NON-NLS-1$ } if (ceil < this.extra[0].length) { pot += ",..."; //$NON-NLS-1$ return def + pot + "], reachable:" + ((this.tagBits & UNREACHABLE) == 0) //$NON-NLS-1$ + ", no null info>"; //$NON-NLS-1$ tucking would do the same (if we focus on the tightest SESE, the one without the return statement) KH03 would keep the loop as is, and would move the last assignment to pot to the after part
11
Sliding {result}
12
method_body: { int length = this.keyTable.length,index = CharOperation.hashCode(key) % length; while (this.keyTable[index] != null) { if (CharOperation.equals(this.keyTable[index], key)) { result = this.valueTable[index]; break method_body; } if (++index == length) { // faster than modulo index = 0; } result = -value; // negative when added (value is assumed to be > 0) method_body: { int length = this.keyTable.length,index = CharOperation.hashCode(key) % length; while (this.keyTable[index] != null) { if (CharOperation.equals(this.keyTable[index], key)) { break method_body; } if (++index == length) { // faster than modulo index = 0; } this.keyTable[index] = key; this.valueTable[index] = value; // assumes the threshold is never equal to the size of the table if (++this.elementSize > this.threshold) rehash();
13
String def = "FlowInfo<def:[" + this.definiteInits; //$NON-NLS-1$
int i, ceil; for (i = 0, ceil = this.extra[0].length > 3 ? 3 : this.extra[0].length; i < ceil; i++) { def += "," + this.extra[0][i]; //$NON-NLS-1$ } if (ceil < this.extra[0].length) { def += ",..."; //$NON-NLS-1$ String pot = "], pot:[" + this.potentialInits; //$NON-NLS-1$ for (i = 0, ceil = this.extra[0].length > 3 ? 3 : this.extra[0].length; i < ceil; i++) { pot += "," + this.extra[1][i]; //$NON-NLS-1$ } if (ceil < this.extra[0].length) { pot += ",..."; //$NON-NLS-1$ return def + pot + "], reachable:" + ((this.tagBits & UNREACHABLE) == 0) //$NON-NLS-1$ + ", no null info>"; //$NON-NLS-1$ tucking would do the same (if we focus on the tightest SESE, the one without the return statement) KH03 would keep the loop as is, and would move the last assignment to pot to the after part
14
Sliding {index}
15
Object key1Value = this.nameAndTypeCacheForFieldsAndMethods.get(key1);
if (key1Value == null) { index = -value; } else if (key1Value instanceof CachedIndexEntry) { CachedIndexEntry entry = (CachedIndexEntry) key1Value; if (CharOperation.equals(key2, entry.signature)) { index = entry.index; } else { CharArrayCache charArrayCache = new CharArrayCache(); charArrayCache.putIfAbsent(entry.signature, entry.index); index = charArrayCache.putIfAbsent(key2, value); } CharArrayCache charArrayCache = (CharArrayCache) key1Value; key1Value = this.nameAndTypeCacheForFieldsAndMethods.get(key1); if (key1Value == null) { CachedIndexEntry cachedIndexEntry = new CachedIndexEntry(key2, value); this.nameAndTypeCacheForFieldsAndMethods.put(key1, cachedIndexEntry); } else if (key1Value instanceof CachedIndexEntry) { CachedIndexEntry entry = (CachedIndexEntry) key1Value; if (CharOperation.equals(key2, entry.signature)) { } else { CharArrayCache charArrayCache = new CharArrayCache(); charArrayCache.putIfAbsent(entry.signature, entry.index); charArrayCache.putIfAbsent(key2, value); this.nameAndTypeCacheForFieldsAndMethods.put(key1, charArrayCache); } CharArrayCache charArrayCache = (CharArrayCache) key1Value;
16
Object key1Value = this.nameAndTypeCacheForFieldsAndMethods.get(key1);
if (key1Value == null) { index = -value; } else if (key1Value instanceof CachedIndexEntry) { CachedIndexEntry entry = (CachedIndexEntry) key1Value; if (CharOperation.equals(key2, entry.signature)) { index = entry.index; } else { CharArrayCache charArrayCache = new CharArrayCache(); charArrayCache.putIfAbsent(entry.signature, entry.index); index = charArrayCache.putIfAbsent(key2, value); } CharArrayCache charArrayCache = (CharArrayCache) key1Value; key1Value = this.nameAndTypeCacheForFieldsAndMethods.get(key1); if (key1Value == null) { CachedIndexEntry cachedIndexEntry = new CachedIndexEntry(key2, value); this.nameAndTypeCacheForFieldsAndMethods.put(key1, cachedIndexEntry); } else if (key1Value instanceof CachedIndexEntry) { CachedIndexEntry entry = (CachedIndexEntry) key1Value; if (CharOperation.equals(key2, entry.signature)) { } else { CharArrayCache charArrayCache = new CharArrayCache(); charArrayCache.putIfAbsent(entry.signature, entry.index); charArrayCache.putIfAbsent(key2, value); this.nameAndTypeCacheForFieldsAndMethods.put(key1, charArrayCache); } CharArrayCache charArrayCache = (CharArrayCache) key1Value;
17
Sliding {index,key1Value.*}
18
Object key1Value = this.nameAndTypeCacheForFieldsAndMethods.get(key1);
if (key1Value == null) { index = -value; } else if (key1Value instanceof CachedIndexEntry) { CachedIndexEntry entry = (CachedIndexEntry) key1Value; if (CharOperation.equals(key2, entry.signature)) { index = entry.index; } else { CharArrayCache charArrayCache = new CharArrayCache(); charArrayCache.putIfAbsent(entry.signature, entry.index); index = charArrayCache.putIfAbsent(key2, value); } CharArrayCache charArrayCache = (CharArrayCache) key1Value; key1Value = this.nameAndTypeCacheForFieldsAndMethods.get(key1); if (key1Value == null) { CachedIndexEntry cachedIndexEntry = new CachedIndexEntry(key2, value); this.nameAndTypeCacheForFieldsAndMethods.put(key1, cachedIndexEntry); } else if (key1Value instanceof CachedIndexEntry) { CachedIndexEntry entry = (CachedIndexEntry) key1Value; if (CharOperation.equals(key2, entry.signature)) { } else { CharArrayCache charArrayCache = new CharArrayCache(); charArrayCache.putIfAbsent(entry.signature, entry.index); charArrayCache.putIfAbsent(key2, value); this.nameAndTypeCacheForFieldsAndMethods.put(key1, charArrayCache); }
19
Object key1Value = this.nameAndTypeCacheForFieldsAndMethods.get(key1);
if (key1Value == null) { index = -value; } else if (key1Value instanceof CachedIndexEntry) { CachedIndexEntry entry = (CachedIndexEntry) key1Value; if (CharOperation.equals(key2, entry.signature)) { index = entry.index; } else { CharArrayCache charArrayCache = new CharArrayCache(); charArrayCache.putIfAbsent(entry.signature, entry.index); index = charArrayCache.putIfAbsent(key2, value); } CharArrayCache charArrayCache = (CharArrayCache) key1Value; key1Value = this.nameAndTypeCacheForFieldsAndMethods.get(key1); if (key1Value == null) { CachedIndexEntry cachedIndexEntry = new CachedIndexEntry(key2, value); this.nameAndTypeCacheForFieldsAndMethods.put(key1, cachedIndexEntry); } else if (key1Value instanceof CachedIndexEntry) { CachedIndexEntry entry = (CachedIndexEntry) key1Value; if (CharOperation.equals(key2, entry.signature)) { } else { CharArrayCache charArrayCache = new CharArrayCache(); charArrayCache.putIfAbsent(entry.signature, entry.index); charArrayCache.putIfAbsent(key2, value); this.nameAndTypeCacheForFieldsAndMethods.put(key1, charArrayCache); }
20
Sliding {index,key1Value,key1Value.*}
21
Object key1Value = this.nameAndTypeCacheForFieldsAndMethods.get(key1);
if (key1Value == null) { index = -value; } else if (key1Value instanceof CachedIndexEntry) { CachedIndexEntry entry = (CachedIndexEntry) key1Value; if (CharOperation.equals(key2, entry.signature)) { index = entry.index; } else { CharArrayCache charArrayCache = new CharArrayCache(); charArrayCache.putIfAbsent(entry.signature, entry.index); index = charArrayCache.putIfAbsent(key2, value); } CharArrayCache charArrayCache = (CharArrayCache) key1Value; if (key1Value == null) { CachedIndexEntry cachedIndexEntry = new CachedIndexEntry(key2, value); this.nameAndTypeCacheForFieldsAndMethods.put(key1, cachedIndexEntry); } else if (key1Value instanceof CachedIndexEntry) { CachedIndexEntry entry = (CachedIndexEntry) key1Value; if (CharOperation.equals(key2, entry.signature)) { } else { CharArrayCache charArrayCache = new CharArrayCache(); charArrayCache.putIfAbsent(entry.signature, entry.index); charArrayCache.putIfAbsent(key2, value); this.nameAndTypeCacheForFieldsAndMethods.put(key1, charArrayCache); }
22
Object key1Value = this.nameAndTypeCacheForFieldsAndMethods.get(key1);
if (key1Value == null) { index = -value; } else if (key1Value instanceof CachedIndexEntry) { CachedIndexEntry entry = (CachedIndexEntry) key1Value; if (CharOperation.equals(key2, entry.signature)) { index = entry.index; } else { CharArrayCache charArrayCache = new CharArrayCache(); charArrayCache.putIfAbsent(entry.signature, entry.index); index = charArrayCache.putIfAbsent(key2, value); } CharArrayCache charArrayCache = (CharArrayCache) key1Value; if (key1Value == null) { CachedIndexEntry cachedIndexEntry = new CachedIndexEntry(key2, value); this.nameAndTypeCacheForFieldsAndMethods.put(key1, cachedIndexEntry); } else if (key1Value instanceof CachedIndexEntry) { CachedIndexEntry entry = (CachedIndexEntry) key1Value; if (CharOperation.equals(key2, entry.signature)) { } else { CharArrayCache charArrayCache = new CharArrayCache(); charArrayCache.putIfAbsent(entry.signature, entry.index); charArrayCache.putIfAbsent(key2, value); this.nameAndTypeCacheForFieldsAndMethods.put(key1, charArrayCache); }
23
Sliding {index,key1Value} after a step of SQfM on CharArrayCache
Sliding {index,key1Value} after a step of SQfM on CharArrayCache.putIfAbsent()
24
Object key1Value = this.nameAndTypeCacheForFieldsAndMethods.get(key1);
if (key1Value == null) { index = -value; } else if (key1Value instanceof CachedIndexEntry) { CachedIndexEntry entry = (CachedIndexEntry) key1Value; if (CharOperation.equals(key2, entry.signature)) { index = entry.index; } else { CharArrayCache charArrayCache = new CharArrayCache(); charArrayCache.putIfAbsent(entry.signature, entry.index); index = charArrayCache.negativeValueIfAbsent(key2, value); } CharArrayCache charArrayCache = (CharArrayCache) key1Value; if (key1Value == null) { CachedIndexEntry cachedIndexEntry = new CachedIndexEntry(key2, value); this.nameAndTypeCacheForFieldsAndMethods.put(key1, cachedIndexEntry); } else if (key1Value instanceof CachedIndexEntry) { CachedIndexEntry entry = (CachedIndexEntry) key1Value; if (CharOperation.equals(key2, entry.signature)) { } else { CharArrayCache charArrayCache = new CharArrayCache(); charArrayCache.putIfAbsent(entry.signature, entry.index); charArrayCache.putIfAbsent(key2, value); this.nameAndTypeCacheForFieldsAndMethods.put(key1, charArrayCache); } CharArrayCache charArrayCache = (CharArrayCache) key1Value;
25
Object key1Value = this.nameAndTypeCacheForFieldsAndMethods.get(key1);
if (key1Value == null) { index = -value; } else if (key1Value instanceof CachedIndexEntry) { CachedIndexEntry entry = (CachedIndexEntry) key1Value; if (CharOperation.equals(key2, entry.signature)) { index = entry.index; } else { CharArrayCache charArrayCache = new CharArrayCache(); charArrayCache.putIfAbsent(entry.signature, entry.index); index = charArrayCache.negativeValueIfAbsent(key2, value); } CharArrayCache charArrayCache = (CharArrayCache) key1Value; if (key1Value == null) { CachedIndexEntry cachedIndexEntry = new CachedIndexEntry(key2, value); this.nameAndTypeCacheForFieldsAndMethods.put(key1, cachedIndexEntry); } else if (key1Value instanceof CachedIndexEntry) { CachedIndexEntry entry = (CachedIndexEntry) key1Value; if (CharOperation.equals(key2, entry.signature)) { } else { CharArrayCache charArrayCache = new CharArrayCache(); charArrayCache.putIfAbsent(entry.signature, entry.index); charArrayCache.putIfAbsent(key2, value); this.nameAndTypeCacheForFieldsAndMethods.put(key1, charArrayCache); } CharArrayCache charArrayCache = (CharArrayCache) key1Value;
26
Sliding {index,key1Value,entry, charArrayCache, charArrayCache
Sliding {index,key1Value,entry, charArrayCache, charArrayCache.*} after a step of de-localization
27
Object key1Value = this.nameAndTypeCacheForFieldsAndMethods.get(key1);
if (key1Value == null) { index = -value; } else if (key1Value instanceof CachedIndexEntry) { CachedIndexEntry entry = (CachedIndexEntry) key1Value; if (CharOperation.equals(key2, entry.signature)) { index = entry.index; } else { CharArrayCache charArrayCache = new CharArrayCache(); charArrayCache.putIfAbsent(entry.signature, entry.index); index = charArrayCache.putIfAbsent(key2, value); } CharArrayCache charArrayCache = (CharArrayCache) key1Value; if (key1Value == null) { CachedIndexEntry cachedIndexEntry = new CachedIndexEntry(key2, value); this.nameAndTypeCacheForFieldsAndMethods.put(key1, cachedIndexEntry); } else if (key1Value instanceof CachedIndexEntry) { if (CharOperation.equals(key2, entry.signature)) { } else { this.nameAndTypeCacheForFieldsAndMethods.put(key1, charArrayCache); }
28
Object key1Value = this.nameAndTypeCacheForFieldsAndMethods.get(key1);
if (key1Value == null) { index = -value; } else if (key1Value instanceof CachedIndexEntry) { CachedIndexEntry entry = (CachedIndexEntry) key1Value; if (CharOperation.equals(key2, entry.signature)) { index = entry.index; } else { CharArrayCache charArrayCache = new CharArrayCache(); charArrayCache.putIfAbsent(entry.signature, entry.index); index = charArrayCache.putIfAbsent(key2, value); } CharArrayCache charArrayCache = (CharArrayCache) key1Value; if (key1Value == null) { CachedIndexEntry cachedIndexEntry = new CachedIndexEntry(key2, value); this.nameAndTypeCacheForFieldsAndMethods.put(key1, cachedIndexEntry); } else if (key1Value instanceof CachedIndexEntry) { if (CharOperation.equals(key2, entry.signature)) { } else { this.nameAndTypeCacheForFieldsAndMethods.put(key1, charArrayCache); }
29
Statements-Based Sliding (future work)
30
++ + Object key1Value = this.nameAndTypeCacheForFieldsAndMethods.get(key1); if (key1Value == null) { CachedIndexEntry cachedIndexEntry = new CachedIndexEntry(key2, value); index = -value; } else if (key1Value instanceof CachedIndexEntry) { CachedIndexEntry entry = (CachedIndexEntry) key1Value; if (CharOperation.equals(key2, entry.signature)) { index = entry.index; } else { CharArrayCache charArrayCache = new CharArrayCache(); charArrayCache.putIfAbsent(entry.signature, entry.index); index = charArrayCache.putIfAbsent(key2, value); } CharArrayCache charArrayCache = (CharArrayCache) key1Value; if (key1Value == null) { this.nameAndTypeCacheForFieldsAndMethods.put(key1, cachedIndexEntry); } else if (key1Value instanceof CachedIndexEntry) { if (CharOperation.equals(key2, entry.signature)) { } else { this.nameAndTypeCacheForFieldsAndMethods.put(key1, charArrayCache); } This scenario is future work: sliding for variables works well on this example but not on all For example, in a near-miss clone, we have a new assignment to key1Value if it is null If that new is not part of the duplicated code, and the only way to reuse key1Value is by including it in the set of variables for sliding, V, we will get the new in the slicing against our intention A reduction of lines-based sliding to use variable-based sliding is the topic of Moshe Zemah’s ongoing Master’s project
31
++ Object key1Value = this.nameAndTypeCacheForFieldsAndMethods.get(key1); if (key1Value == null) { CachedIndexEntry cachedIndexEntry = new CachedIndexEntry(key2, value); index = -value; } else if (key1Value instanceof CachedIndexEntry) { CachedIndexEntry entry = (CachedIndexEntry) key1Value; if (CharOperation.equals(key2, entry.signature)) { index = entry.index; } else { CharArrayCache charArrayCache = new CharArrayCache(); charArrayCache.putIfAbsent(entry.signature, entry.index); index = charArrayCache.putIfAbsent(key2, value); } CharArrayCache charArrayCache = (CharArrayCache) key1Value; + if (key1Value == null) { this.nameAndTypeCacheForFieldsAndMethods.put(key1, cachedIndexEntry); } else if (key1Value instanceof CachedIndexEntry) { if (CharOperation.equals(key2, entry.signature)) { } else { this.nameAndTypeCacheForFieldsAndMethods.put(key1, charArrayCache); } This scenario is future work: sliding for variables works well on this example but not on all For example, in a near-miss clone, we have a new assignment to key1Value if it is null If that new is not part of the duplicated code, and the only way to reuse key1Value is by including it in the set of variables for sliding, V, we will get the new in the slicing against our intention A reduction of lines-based sliding to use variable-based sliding is the topic of Moshe Zemah’s ongoing Master’s project
32
Sliding-Based Split Loop
Perform sliding on the loop fragment choosing a subset V of the loop’s results Avoid unnecessary code duplication by adding to V all loop results whose addition will not increase the size of the first resulting loop If sliding fails update the code to avoid the failure and repeat this step, or choose a different loop to split Compile and test
33
String def = "FlowInfo<def:[" + this.definiteInits; //$NON-NLS-1$
String pot = "], pot:[" + this.potentialInits; //$NON-NLS-1$ int i, ceil; for (i = 0, ceil = this.extra[0].length > 3 ? 3 : this.extra[0].length; i < ceil; i++) { def += "," + this.extra[0][i]; //$NON-NLS-1$ pot += "," + this.extra[1][i]; //$NON-NLS-1$ } if (ceil < this.extra[0].length) { def += ",..."; //$NON-NLS-1$ pot += ",..."; //$NON-NLS-1$ return def + pot + "], reachable:" + ((this.tagBits & UNREACHABLE) == 0) //$NON-NLS-1$ + ", no null info>"; //$NON-NLS-1$ A slice motion of the code for computing the temp def org.eclipse.jdt.internal.compiler.flow.UnconditionalFlowInfo.toString()
34
int i, ceil; for (i = 0, ceil = this.extra[0].length > 3 ? 3 : this.extra[0].length; i < ceil; i++) { def += "," + this.extra[0][i]; //$NON-NLS-1$ } for (i = 0, ceil = this.extra[0].length > 3 ? 3 : this.extra[0].length; i < ceil; i++) { pot += "," + this.extra[1][i]; //$NON-NLS-1$ }
35
def += "," + this.extra[0][i]; //$NON-NLS-1$ }
int i, ceil; for (i = 0, ceil = this.extra[0].length > 3 ? 3 : this.extra[0].length; i < ceil; i++) { def += "," + this.extra[0][i]; //$NON-NLS-1$ } for (i = 0, ceil = this.extra[0].length > 3 ? 3 : this.extra[0].length; i < ceil; i++) { pot += "," + this.extra[1][i]; //$NON-NLS-1$ } tucking would get rejected due to the definition of ceil on both sides, with ceil being live on exit yet, the tightest SESE region could keep the initializing definitions of I and ceil above the scope, to successfully split the loop KH03 would keep the code as is, by promoting the definition of pot
36
entry for (…) {def} def+= … {pot} pot+= … {pot} exit {def} {def,pot}
37
entry for (…) def+= … pot+= … exit {def} {pot} {pot} V = {def}
FlowToExit_{NonV} = {(“pot += …”,exit)} {def} {def,pot}
38
entry entry for (…) def+= … exit for (…) def+= … pot+= … exit {def}
N_V = {entry,”for (…)”,”def += …”,exit} {def} {def,pot}
39
entry for (…) def+= … pot+= … exit {def} {pot} {pot} {def} {def,pot}
NonFinalUsesAtNode = { (“for (…)”,{i,ceil?)}, (“def += …”,{def}, (“pot += …”,{pot} } FlowToFinalUse_V = {(“def += …”,exit)} Note that the flow dependence edge from “def += …” to itself is not to a final use of “def” N_{CoV} = {entry,”for (…)”,”pot += …”,exit} Pen1 is empty as there is no def of “def” in any member of N_{CoV} (besides entry, which should be excluded) The exclusion of entry from this check was wrongly absent from the paper There’s no need to exclude the exit too, yet no harm in including it either; this is because Def(exit) is empty by definition Pen2 is empty as the only use of “def” in the co-slice is exit, where it is certainly final There’s no need yet no harm either, in the case of Pen2, to include the entry and exit nodes in the check This is because no variable is used in the entry, and all variables have their final value in the exit Pen3 is empty too InputToCoSliceNonV = {pot} + some members of this.* that are not defined in this fragment and are therefore irrelevant here Searching for members of InputToCoSliceNonV in the Def(n) of every node n in N_V we find “pot” only in Def(entry) But, again, entry should be excluded from investigation as it does not really modify the value of variables, it merely stands for their initialization {def} {def,pot}
40
entry for (…) pot+= … exit entry for (…) def+= … pot+= exit {def}
41
A Sliding-Based RTwQ Example
Sliding (isolate the full slice of def) Extract Method (turn the slice of def into a method and invoke it to initialize def Inline Temp (replace references to def with calls to the new method)
42
String def = "FlowInfo<def:[" + this.definiteInits; //$NON-NLS-1$
String pot = "], pot:[" + this.potentialInits; //$NON-NLS-1$ int i, ceil; for (i = 0, ceil = this.extra[0].length > 3 ? 3 : this.extra[0].length; i < ceil; i++) { def += "," + this.extra[0][i]; //$NON-NLS-1$ pot += "," + this.extra[1][i]; //$NON-NLS-1$ } if (ceil < this.extra[0].length) { def += ",..."; //$NON-NLS-1$ pot += ",..."; //$NON-NLS-1$ return def + pot + "], reachable:" + ((this.tagBits & UNREACHABLE) == 0) //$NON-NLS-1$ + ", no null info>"; //$NON-NLS-1$ org.eclipse.jdt.internal.compiler.flow.UnconditionalFlowInfo.toString()
43
String def = "FlowInfo<def:[" + this.definiteInits; //$NON-NLS-1$
int i, ceil; for (i = 0, ceil = this.extra[0].length > 3 ? 3 : this.extra[0].length; i < ceil; i++) { def += "," + this.extra[0][i]; //$NON-NLS-1$ } if (ceil < this.extra[0].length) { def += ",..."; //$NON-NLS-1$ String pot = "], pot:[" + this.potentialInits; //$NON-NLS-1$ for (i = 0, ceil = this.extra[0].length > 3 ? 3 : this.extra[0].length; i < ceil; i++) { pot += "," + this.extra[1][i]; //$NON-NLS-1$ } if (ceil < this.extra[0].length) { pot += ",..."; //$NON-NLS-1$ return def + pot + "], reachable:" + ((this.tagBits & UNREACHABLE) == 0) //$NON-NLS-1$ + ", no null info>"; //$NON-NLS-1$ Anti deps: {(for (…),for (…),{i,ceil?}), (“def+=…”, “def+=…”,{def}), (“def+=…”, “for (…)”,{i}), (“pot+=…”, “pot+=…”,{pot}), (“pot+=…”, “for (…)”,{i}), (“def+=…2”, “def+=…2”,{def}), (“pot+=…2”, “pot+=…2”,{pot})} NonFinalUsesAtNode = { (“for (…)”,{i,ceil?)}, (“def += …”,{def}, (“pot += …”,{pot} } (“def += …2”,{def}, (“pot += …2”,{pot} } FlowToFinalUse_V = {(“def=…”,”return …”), (“def = …”,exit), (“def += …”,”return …”), (“def += …”,exit), (“def += …2”,”return …”), (“def += …”,exit)} N_{CoV} = {entry, “pot=…”, ”for (…)”,”pot += …”, ”if (…)”,”pot += …2”,”return …”,exit} Pen1 is again empty as there is no def of “def” in any member of N_{CoV} (besides entry, which should be excluded) Pen2 is again empty as the two uses of “def” in the co-slice are in “return …” and exit, where “def” is final (no anti dep due to “def” from those) Pen3 is again empty too InputToCoSliceNonV includes only members of this.* that are not defined in this fragment and are therefore irrelevant here So the intersection of InputToCoSliceNonV and Def(n) of every node n in N_V except entry is therefore empty
44
entry def=… pot=… for (…) def+= … pot+= … if (…) def+= … pot+= …
{retval} return … exit
45
entry def=… pot=… for (…) def+= … pot+= … if (…) def+= … pot+= …
{retval} return … exit
46
entry entry for (…) def+= … exit if (…) def=… def=… pot=… for (…)
{retval} return … exit
47
entry for (…) def+= … exit if (…) def=…
48
entry def=… pot=… for (…) def+= … pot+= … if (…) def+= … pot+= …
{retval} return … exit
49
entry entry for (…) pot+= … exit if (…) pot=… return def=… pot=…
{retval} return … exit
50
entry for (…) pot+= … exit if (…) pot=… return
51
Before Extract Method {...
String def = "FlowInfo<def:[" + this.definiteInits; //$NON-NLS-1$ int i, ceil; for (i = 0, ceil = this.extra[0].length > 3 ? 3 : this.extra[0].length; i < ceil; i++) { def += "," + this.extra[0][i]; //$NON-NLS-1$ } if (ceil < this.extra[0].length) { def += ",..."; //$NON-NLS-1$ String pot = "], pot:[" + this.potentialInits; //$NON-NLS-1$ pot += "," + this.extra[1][i]; //$NON-NLS-1$ pot += ",..."; //$NON-NLS-1$ return def + pot + "], reachable:" + ((this.tagBits & UNREACHABLE) == 0) //$NON-NLS-1$ + ", no null info>"; //$NON-NLS-1$ ...}
52
After Extract Method {... String def = def(); int i, ceil;
String pot = "], pot:[" + this.potentialInits; //$NON-NLS-1$ for (i = 0, ceil = this.extra[0].length > 3 ? 3 : this.extra[0].length; i < ceil; i++) { pot += "," + this.extra[1][i]; //$NON-NLS-1$ } if (ceil < this.extra[0].length) { pot += ",..."; //$NON-NLS-1$ return def + pot + "], reachable:" + ((this.tagBits & UNREACHABLE) == 0) //$NON-NLS-1$ + ", no null info>"; //$NON-NLS-1$ ...} private int def() { String def = "FlowInfo<def:[" + this.definiteInits; //$NON-NLS-1$ def += "," + this.extra[0][i]; //$NON-NLS-1$ def += ",..."; //$NON-NLS-1$ return def;
53
After Inline Temp {... int i, ceil;
String pot = "], pot:[" + this.potentialInits; //$NON-NLS-1$ for (i = 0, ceil = this.extra[0].length > 3 ? 3 : this.extra[0].length; i < ceil; i++) { pot += "," + this.extra[1][i]; //$NON-NLS-1$ } if (ceil < this.extra[0].length) { pot += ",..."; //$NON-NLS-1$ return def() + pot + "], reachable:" + ((this.tagBits & UNREACHABLE) == 0) //$NON-NLS-1$ + ", no null info>"; //$NON-NLS-1$ ...}
54
After RTwQ for pot {... return def() + pot()
+ "], reachable:" + ((this.tagBits & UNREACHABLE) == 0) //$NON-NLS-1$ + ", no null info>"; //$NON-NLS-1$ ...} private int pot() { int i, ceil; String pot = "], pot:[" + this.potentialInits; //$NON-NLS-1$ for (i = 0, ceil = this.extra[0].length > 3 ? 3 : this.extra[0].length; i < ceil; i++) { pot += "," + this.extra[1][i]; //$NON-NLS-1$ } if (ceil < this.extra[0].length) { pot += ",..."; //$NON-NLS-1$ return pot;
55
Before Reuse of def() and pot()
def += "," + this.extra[0][i]; //$NON-NLS-1$ pot += "," + this.extra[1][i]; //$NON-NLS-1$ nullS += "," + this.extra[2][i] //$NON-NLS-1$ + this.extra[3][i] + this.extra[4][i] + this.extra[5][i]; } if (ceil < this.extra[0].length) { def += ",..."; //$NON-NLS-1$ pot += ",..."; //$NON-NLS-1$ nullS += ",..."; //$NON-NLS-1$ return def + pot + "], reachable:" + ((this.tagBits & UNREACHABLE) == 0) //$NON-NLS-1$ + nullS + "]>"; //$NON-NLS-1$ else { if (this.extra == null) { return ...; //$NON-NLS-1$ return def() + pot() + ", no null info>"; //$NON-NLS-1$ }...
56
After Reuse of def() and pot()
for (...) { nullS += "," + this.extra[2][i] //$NON-NLS-1$ + this.extra[3][i] + this.extra[4][i] + this.extra[5][i]; } if (ceil < this.extra[0].length) { nullS += ",..."; //$NON-NLS-1$ return def() + pot() + "], reachable:" + ((this.tagBits & UNREACHABLE) == 0) //$NON-NLS-1$ + nullS + "]>"; //$NON-NLS-1$ else { if (this.extra == null) { return ...; //$NON-NLS-1$ + ", no null info>"; //$NON-NLS-1$ }... The loop and conditional are duplicated now 3 times instead of 2 (so 6 statements instead of 4) The exclusive 3 statements in the computation of def are no longer duplicated (3 instead of 6) The exclusive 3 statements in the computation of pot are no longer duplicated (3 instead of 6) Total: added 2 statements and removed 6!
57
A Sliding-Based SQfM Example
Preparation for sliding Add a result variable Add a labeled block, replacing each return with an assignment to result and a break Sliding (isolate the slice of result) Extract Method *2 Undo Step 1 Remove the labeled blocks Replace the temp result with returns Take care to make the modifier’s return empty Inline Method The sliding duplicates the labeled block, so must rename the label Note that M-before-Q would not be trivial
58
public int putIfAbsent(char[] key, int value) {
int length = this.keyTable.length,index = CharOperation.hashCode(key) % length; while (this.keyTable[index] != null) { if (CharOperation.equals(this.keyTable[index], key)) return this.valueTable[index]; if (++index == length) { // faster than modulo index = 0; } this.keyTable[index] = key; this.valueTable[index] = value; // assumes the threshold is never equal to the size of the table if (++this.elementSize > this.threshold) rehash(); return -value; // negative when added (value is assumed to be > 0) org.eclipse.jdt.internal.compiler.codegen.CharArrayCache
59
public int putIfAbsent(char[] key, int value) {
int result; method_body: { int length = this.keyTable.length,index = CharOperation.hashCode(key) % length; while (this.keyTable[index] != null) { if (CharOperation.equals(this.keyTable[index], key)) { result = this.valueTable[index]; break method_body; } if (++index == length) { // faster than modulo index = 0; } this.keyTable[index] = key; this.valueTable[index] = value; // assumes the threshold is never equal to the size of the table if (++this.elementSize > this.threshold) rehash(); result = -value; // negative when added (value is assumed to be > 0) return result;
60
public int putIfAbsent(char[] key, int value) { int result;
method_body: { int length = this.keyTable.length,index = CharOperation.hashCode(key) % length; while (this.keyTable[index] != null) { if (CharOperation.equals(this.keyTable[index], key)) { result = this.valueTable[index]; break method_body; } if (++index == length) { // faster than modulo index = 0; } this.keyTable[index] = key; this.valueTable[index] = value; // assumes the threshold is never equal to the size of the table if (++this.elementSize > this.threshold) rehash(); result = -value; // negative when added (value is assumed to be > 0) return result; tucking would do the same (if we focus on the tightest SESE, the one without the return statement) KH03 would promote everything
61
slice: { int length = this.keyTable.length,index = CharOperation.hashCode(key) % length; while (this.keyTable[index] != null) { if (CharOperation.equals(this.keyTable[index], key)) { result = this.valueTable[index]; break slice; } if (++index == length) { // faster than modulo index = 0; } result = -value; // negative when added (value is assumed to be > 0) co_slice: { int length = this.keyTable.length,index = CharOperation.hashCode(key) % length; while (this.keyTable[index] != null) { if (CharOperation.equals(this.keyTable[index], key)) { break co_slice; } if (++index == length) { // faster than modulo index = 0; } this.keyTable[index] = key; this.valueTable[index] = value; // assumes the threshold is never equal to the size of the table if (++this.elementSize > this.threshold) rehash();
62
public int putIfAbsent_refactored(char[] key, int value) { // deprecated?
} int result = negativeValueIfAbsent(key, value); putIfAbsent(key, value); return result; public int negativeValueIfAbsent(char[] key, int value) { int length = this.keyTable.length,index = CharOperation.hashCode(key) % length; while (this.keyTable[index] != null) { if (CharOperation.equals(this.keyTable[index], key)) return this.valueTable[index]; if (++index == length) { // faster than modulo index = 0; } return -value; // negative when added (value is assumed to be > 0) public void putIfAbsent(char[] key, int value) { int length = this.keyTable.length,index = CharOperation.hashCode(key) % length; while (this.keyTable[index] != null) { if (CharOperation.equals(this.keyTable[index], key)) return; if (++index == length) { // faster than modulo index = 0; } this.keyTable[index] = key; this.valueTable[index] = value; // assumes the threshold is never equal to the size of the table if (++this.elementSize > this.threshold) rehash();
63
Refactored Invocations
public int putInNameAndTypeCacheIfAbsent(final char[] key1, final char[] key2, int value) { int index; Object key1Value = this.nameAndTypeCacheForFieldsAndMethods.get(key1); if (key1Value == null) { CachedIndexEntry cachedIndexEntry = new CachedIndexEntry(key2, value); index = -value; this.nameAndTypeCacheForFieldsAndMethods.put(key1, cachedIndexEntry); } else if (key1Value instanceof CachedIndexEntry) { CachedIndexEntry entry = (CachedIndexEntry) key1Value; if (CharOperation.equals(key2, entry.signature)) { index = entry.index; } else { CharArrayCache charArrayCache = new CharArrayCache(); charArrayCache.putIfAbsent(entry.signature, entry.index); index = charArrayCache.negativeValueIfAbsent(key2, value); charArrayCache.putIfAbsent(key2, value); this.nameAndTypeCacheForFieldsAndMethods.put(key1, charArrayCache); } CharArrayCache charArrayCache = (CharArrayCache) key1Value; return index;
64
Another SQfM Example On the modified method with invocations of the previously separated Q and M This refactoring case has been enabled by the previous one Again, M-before-Q would not be trivial
65
key1Value = this.nameAndTypeCacheForFieldsAndMethods.get(key1);
public int putInNameAndTypeCacheIfAbsent(final char[] key1, final char[] key2, int value) { } key1Value = this.nameAndTypeCacheForFieldsAndMethods.get(key1); if (key1Value == null) { CachedIndexEntry cachedIndexEntry = new CachedIndexEntry(key2, value); this.nameAndTypeCacheForFieldsAndMethods.put(key1, cachedIndexEntry); } else if (key1Value instanceof CachedIndexEntry) { CachedIndexEntry entry = (CachedIndexEntry) key1Value; if (CharOperation.equals(key2, entry.signature)) { } else { CharArrayCache charArrayCache = new CharArrayCache(); charArrayCache.putIfAbsent(entry.signature, entry.index); charArrayCache.putIfAbsent(key2, value); this.nameAndTypeCacheForFieldsAndMethods.put(key1, charArrayCache); } CharArrayCache charArrayCache = (CharArrayCache) key1Value; return index; int index; Object key1Value = this.nameAndTypeCacheForFieldsAndMethods.get(key1); if (key1Value == null) { index = -value; } else if (key1Value instanceof CachedIndexEntry) { CachedIndexEntry entry = (CachedIndexEntry) key1Value; if (CharOperation.equals(key2, entry.signature)) { index = entry.index; } else { CharArrayCache charArrayCache = new CharArrayCache(); charArrayCache.putIfAbsent(entry.signature, entry.index); index = charArrayCache.negativeValueIfAbsent(key2, value); } CharArrayCache charArrayCache = (CharArrayCache) key1Value; tucking would fail even on the tightest SESE (the one without the return statement) due to definitions on both sides of the live-on-exit variables index and charArrayCache.* KH03, if applied on our PDG, would move all non-marked statements (those not on the slice of index) to the after part, along with all conditionals, expecting illegal reuse of the locals entry and the two charArrayCache One may prefer to make it working by de-localizing those variables This way we can avoid the duplication of 5 statements But then the subsequent Extract Method step would fail due to ambiguous result Sliding duplicates 5 definition statements + 3 predicates/conditionals The 12-line slice of index turns out to be cloned; so the addition of 8 statements, twice, followed by the removal of 12 statements, leads to additional 4 duplicated statements and reduced duplication of 4 statements (the assignments to index) The elimination of that clone is another motivation for the Extract Method step, regardless of the goal of SQfM, causing failure to the KH03-based approach! TODO: a natural next step is to apeak of sliding’s current limitation, not being able to support extraction of marked lines, therefore not supporting all cases of the clone elimination scenario [future work in Moshe’s project] TODO: speak also of what would have happened had we not performed the preliminary SQfM step
66
private int putInNameAndTypeCacheIfAbsent(final char[] key1, final char[] key2, int value) {
} int index; Object key1Value = this. nameAndTypeCacheForFieldsAndMethods.get(key1); if (key1Value == null) { index = -value; } else if (key1Value instanceof CachedIndexEntry) { CachedIndexEntry entry = (CachedIndexEntry) key1Value; if (CharOperation.equals(key2, entry.signature)) { index = entry.index; } else { CharArrayCache charArrayCache = new CharArrayCache(); charArrayCache.putIfAbsent(entry.signature, entry.index); index = charArrayCache.negativeValueIfAbsent(key2, value); } CharArrayCache charArrayCache = (CharArrayCache) key1Value; key1Value = this.nameAndTypeCacheForFieldsAndMethods.get(key1); if (key1Value == null) { CachedIndexEntry cachedIndexEntry = new CachedIndexEntry(key2, value); this.nameAndTypeCacheForFieldsAndMethods.put(key1, cachedIndexEntry); } else if (key1Value instanceof CachedIndexEntry) { CachedIndexEntry entry = (CachedIndexEntry) key1Value; if (CharOperation.equals(key2, entry.signature)) { } else { CharArrayCache charArrayCache = new CharArrayCache(); charArrayCache.putIfAbsent(entry.signature, entry.index); charArrayCache.putIfAbsent(key2, value); this.nameAndTypeCacheForFieldsAndMethods.put(key1, charArrayCache); } CharArrayCache charArrayCache = (CharArrayCache) key1Value; return index;
67
private int negativeValueIfAbsent(final char[] key1, final char[] key2, int value) {
int index; Object key1Value = this. nameAndTypeCacheForFieldsAndMethods.get(key1); if (key1Value == null) { index = -value; } else if (key1Value instanceof CachedIndexEntry) { CachedIndexEntry entry = (CachedIndexEntry) key1Value; if (CharOperation.equals(key2, entry.signature)) { index = entry.index; } else { CharArrayCache charArrayCache = new CharArrayCache(); charArrayCache.putIfAbsent(entry.signature, entry.index); index = charArrayCache.negativeValueIfAbsent(key2, value); } CharArrayCache charArrayCache = (CharArrayCache) key1Value; return index; private void putInNameAndTypeCacheIfAbsent(final char[] key1, final char[] key2, int value) { Object key1Value = this.nameAndTypeCacheForFieldsAndMethods.get(key1); if (key1Value == null) { CachedIndexEntry cachedIndexEntry = new CachedIndexEntry(key2, value); this.nameAndTypeCacheForFieldsAndMethods.put(key1, cachedIndexEntry); } else if (key1Value instanceof CachedIndexEntry) { CachedIndexEntry entry = (CachedIndexEntry) key1Value; if (CharOperation.equals(key2, entry.signature)) { } else { CharArrayCache charArrayCache = new CharArrayCache(); charArrayCache.putIfAbsent(entry.signature, entry.index); charArrayCache.putIfAbsent(key2, value); this.nameAndTypeCacheForFieldsAndMethods.put(key1, charArrayCache); } CharArrayCache charArrayCache = (CharArrayCache) key1Value;
68
public int literalIndexForNameAndType(char[] name, char[] signature) {
int index = negativeValueIfAbsent(name, signature, this.currentIndex); putInNameAndTypeCacheIfAbsent(name, signature, this.currentIndex); if (index < 0) { // The entry doesn't exit yet this.currentIndex++; if ((index = -index) > 0xFFFF){ this.classFile.referenceBinding.scope.problemReporter(). noMoreAvailableSpaceInConstantPool(this.classFile.referenceBinding.scope.referenceType()); } int length = this.offsets.length; if (length <= index) { // resize System.arraycopy(this.offsets, 0, (this.offsets = new int[index * 2]), 0, length); this.offsets[index] = this.currentOffset; writeU1(NameAndTypeTag); int nameIndexOffset = this.currentOffset; if (this.currentOffset + 4 >= this.poolContent.length) { resizePoolContents(4); this.currentOffset+=4; final int nameIndex = literalIndex(name); final int typeIndex = literalIndex(signature); this.poolContent[nameIndexOffset++] = (byte) (nameIndex >> 8); this.poolContent[nameIndexOffset++] = (byte) nameIndex; this.poolContent[nameIndexOffset++] = (byte) (typeIndex >> 8); this.poolContent[nameIndexOffset] = (byte) typeIndex; return index;
69
Preliminary Experimental Results
A total of 55 manual refactorings 23 successful cases of sliding-based RTwQ 31 successful cases of sliding-based SQfM A single problematic case of SQfM Side effect on field returns a new object Calling it twice would return different objects Code size was 6 to 97 statements in scope Average: 23.5 Slice size ranged from 1 to 85 statements Average: 10.4 Co-slice size ranged from 1 to 82 statements Average: 20 (duplicated 6.9) No need for compensation in 44% of the cases (7 RTwQ and 17 SQfM) In all remaining cases variable renaming and simple backup of up to 3 variables proved sufficient Reuse 132 out of the 239 total uses of an extracted variable were uses of a final value, averaging some 2.4 final uses and 1.99 non-final uses per case After sliding, 46 non-final uses were left in the co-slices, leaving us with some 0.85 non-final uses per case
70
Contributions Practical co-slicing and sliding algorithms suitable for the real case of (sequential) Java, building on traditional (backward, static, syntax preserving) slicing and the underlying program dependence graph representation, hence deferring the responsibility for correctness, scalability, and applicability for more languages to the slicer and the dependence graph construction mechanism Both the slice and co-slice are special cases of fine slicing [FASE12] First evidence for the applicability of sliding for solving known refactoring techniques, including a detailed account of how developers can use sliding as a building block for performing such refactorings A preliminary evaluation, having transformed a well-tested massively-used real-life Java code with no detected regression
71
Conclusion To paraphrase Weiser’s seminal work [ICSE’81], sliding is a new way of recomposing programs automatically Limited to code already written, it may prove useful during the refactoring, testing, and maintenance portions of the software life cycle This work concentrated on the basic methods for sliding programs and their embodiment in automatic tools for refactoring Future work on sliding-based programming aids is necessary before the implications of this kind of recomposition are fully known Key side messages: Definition of a co-slice Justify critical steps of reasoning in the algorithm Intra or inter? How deps are computed across calls (ModRef) Aliases are incorporated through data deps (type-based vs. whole program analysis) Complications caused by side effects Results summary Comparison of co-slice size to size of slice of co-V Comparison with previous work Compensation The metaphor
Similar presentations
© 2025 SlidePlayer.com. Inc.
All rights reserved.