Skip to content

Commit c07f723

Browse files
rlublecopybara-github
authored andcommitted
Generalize VariableRenamer to rename variables due to name collisions that might be introduced by other passes.
PiperOrigin-RevId: 826489947
1 parent d244831 commit c07f723

9 files changed

Lines changed: 230 additions & 73 deletions

File tree

translator/src/main/java/com/google/devtools/j2objc/ast/Name.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
package com.google.devtools.j2objc.ast;
1616

17+
import com.google.errorprone.annotations.CanIgnoreReturnValue;
1718
import javax.lang.model.element.Element;
1819
import javax.lang.model.type.TypeMirror;
1920

@@ -50,6 +51,7 @@ public Element getElement() {
5051
return element;
5152
}
5253

54+
@CanIgnoreReturnValue
5355
public Name setElement(Element newElement) {
5456
element = newElement;
5557
return this;

translator/src/main/java/com/google/devtools/j2objc/pipeline/TranslationProcessor.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -210,16 +210,18 @@ public static void applyMutations(
210210
new AbstractMethodRewriter(unit, deadCodeMap).run();
211211
ticker.tick("AbstractMethodRewriter");
212212

213-
new VariableRenamer(unit).run();
214-
ticker.tick("VariableRenamer");
215-
213+
// Before: VariableRenamer - VariableRenamer renames variables in a scope-aware manner.
216214
new InstanceOfPatternRewriter(unit).run();
217215
ticker.tick("InstanceOfPatternRewriter");
218216

217+
// Before: VariableRenamer - VariableRenamer renames variables in a scope-aware manner.
219218
// Rewrite enhanced for loops into correct C code.
220219
new EnhancedForRewriter(unit).run();
221220
ticker.tick("EnhancedForRewriter");
222221

222+
new VariableRenamer(unit).run();
223+
ticker.tick("VariableRenamer");
224+
223225
// Before: Autoboxer - Must generate implementations so autoboxing can be applied to result.
224226
new LambdaRewriter(unit).run();
225227
ticker.tick("LambdaRewriter");

translator/src/main/java/com/google/devtools/j2objc/translate/EnhancedForRewriter.java

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,9 +78,7 @@ public void endVisit(EnhancedForStatement node) {
7878
} else if (loopVariable.asType().getKind().isPrimitive()) {
7979
boxLoopVariable(node, expressionType, loopVariable);
8080
} else {
81-
VariableElement newLoopVariable = GeneratedVariableElement.mutableCopy(loopVariable)
82-
.setTypeQualifiers("__strong");
83-
node.getParameter().setVariableElement(newLoopVariable);
81+
addStrongQualifierToLoopVariable(node);
8482
}
8583
}
8684

@@ -233,4 +231,23 @@ private Block makeBlock(Statement stmt) {
233231
block.addStatement(stmt);
234232
return block;
235233
}
234+
235+
private void addStrongQualifierToLoopVariable(EnhancedForStatement node) {
236+
VariableElement oldLoopVariable = node.getParameter().getVariableElement();
237+
VariableElement newLoopVariable =
238+
GeneratedVariableElement.mutableCopy(oldLoopVariable).setTypeQualifiers("__strong");
239+
node.getParameter().setVariableElement(newLoopVariable);
240+
241+
// Replace variable references so that the VariableRenamer sees the link between the
242+
// variable declarations and its references.
243+
node.accept(
244+
new UnitTreeVisitor(unit) {
245+
@Override
246+
public void endVisit(SimpleName node) {
247+
if (oldLoopVariable == node.getElement()) {
248+
node.setElement(newLoopVariable);
249+
}
250+
}
251+
});
252+
}
236253
}

translator/src/main/java/com/google/devtools/j2objc/translate/InstanceOfPatternRewriter.java

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,6 @@
4646
public class InstanceOfPatternRewriter extends UnitTreeVisitor {
4747

4848
private Deque<Block> enclosingScopes = new ArrayDeque<>();
49-
private int tempCount;
50-
private int patternCount;
5149

5250
public InstanceOfPatternRewriter(CompilationUnit unit) {
5351
super(unit);
@@ -74,11 +72,6 @@ public void endVisit(InstanceofExpression node) {
7472

7573
VariableElement patternVariable =
7674
((Pattern.BindingPattern) node.getPattern()).getVariable().getVariableElement();
77-
// TODO(b/454053746): Have a general pass that renames variables to avoid collisions.
78-
// Create a unique name to avoid collisions.
79-
nameTable.setVariableName(
80-
patternVariable,
81-
nameTable.getVariableShortName(patternVariable) + "$pattern$" + patternCount++);
8275
enclosingScopes.peek().addStatement(0, new VariableDeclarationStatement(patternVariable, null));
8376

8477
Expression expression = node.getLeftOperand();
@@ -88,9 +81,7 @@ public void endVisit(InstanceofExpression node) {
8881
// that the expression doesn't have side effects and can be evaluated multiple times.
8982
VariableElement tempVariable =
9083
GeneratedVariableElement.newLocalVar(
91-
"tmp$instanceof$" + tempCount++,
92-
node.getLeftOperand().getTypeMirror(),
93-
patternVariable.getEnclosingElement());
84+
"tmp", node.getLeftOperand().getTypeMirror(), patternVariable.getEnclosingElement());
9485
enclosingScopes.peek().addStatement(0, new VariableDeclarationStatement(tempVariable, null));
9586
// tmp = expr
9687
replacement.addExpression(
@@ -99,7 +90,7 @@ public void endVisit(InstanceofExpression node) {
9990
}
10091

10192
replacement.addExpressions(
102-
// patternVariable = expr instanceof T ? (T) expr : null
93+
// patternVariable = expression instanceof T ? (T) expression : null
10394
new Assignment(
10495
new SimpleName(patternVariable),
10596
new ConditionalExpression()

translator/src/main/java/com/google/devtools/j2objc/translate/VariableRenamer.java

Lines changed: 158 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,31 @@
1414

1515
package com.google.devtools.j2objc.translate;
1616

17+
import com.google.common.collect.ImmutableList;
1718
import com.google.devtools.j2objc.ast.AnnotationTypeDeclaration;
19+
import com.google.devtools.j2objc.ast.Block;
20+
import com.google.devtools.j2objc.ast.CatchClause;
1821
import com.google.devtools.j2objc.ast.CompilationUnit;
22+
import com.google.devtools.j2objc.ast.EnhancedForStatement;
1923
import com.google.devtools.j2objc.ast.EnumDeclaration;
24+
import com.google.devtools.j2objc.ast.ForStatement;
2025
import com.google.devtools.j2objc.ast.LambdaExpression;
26+
import com.google.devtools.j2objc.ast.MethodDeclaration;
2127
import com.google.devtools.j2objc.ast.RecordDeclaration;
2228
import com.google.devtools.j2objc.ast.SimpleName;
29+
import com.google.devtools.j2objc.ast.SingleVariableDeclaration;
2330
import com.google.devtools.j2objc.ast.TreeUtil;
2431
import com.google.devtools.j2objc.ast.TypeDeclaration;
2532
import com.google.devtools.j2objc.ast.UnitTreeVisitor;
33+
import com.google.devtools.j2objc.ast.VariableDeclarationFragment;
2634
import com.google.devtools.j2objc.util.ElementUtil;
2735
import com.google.devtools.j2objc.util.ErrorUtil;
28-
import java.util.ArrayList;
36+
import com.google.errorprone.annotations.CanIgnoreReturnValue;
37+
import java.util.ArrayDeque;
38+
import java.util.Deque;
2939
import java.util.HashSet;
30-
import java.util.List;
3140
import java.util.Set;
41+
import javax.lang.model.element.Element;
3242
import javax.lang.model.element.ExecutableElement;
3343
import javax.lang.model.element.TypeElement;
3444
import javax.lang.model.element.VariableElement;
@@ -41,8 +51,37 @@
4151
@SuppressWarnings("UngroupedOverloads")
4252
public class VariableRenamer extends UnitTreeVisitor {
4353

44-
private List<Set<String>> fieldNameStack = new ArrayList<>();
45-
private Set<TypeElement> renamedTypes = new HashSet<>();
54+
private final Deque<Set<String>> fieldNameStack = new ArrayDeque<>();
55+
private final Set<TypeElement> renamedTypes = new HashSet<>();
56+
// Keep track of the variables and names in a scope so we can rename variables in a
57+
// scope-aware manner; start with a sentinel empty scope.
58+
private final Deque<Scope> scopes = new ArrayDeque<>(ImmutableList.of(new Scope()));
59+
60+
// Keep track of variables that are in scope and the names that have already been used.
61+
private static class Scope {
62+
private final Set<VariableElement> variables = new HashSet<>();
63+
private final Set<String> usedNames = new HashSet<>();
64+
65+
private Scope(Scope enclosingScope) {
66+
this.variables.addAll(enclosingScope.variables);
67+
this.usedNames.addAll(enclosingScope.usedNames);
68+
}
69+
70+
private Scope() {}
71+
72+
/**
73+
* Called when a variable reference has been seen.
74+
*
75+
* @return true if its the variable needs renaming, false if it is seen for the first time.
76+
*/
77+
private boolean needsRenaming(VariableElement variable) {
78+
if (!variables.add(variable)) {
79+
// Variable has already been seen in the scope so its name was already resolved.
80+
return false;
81+
}
82+
return !usedNames.add(ElementUtil.getName(variable));
83+
}
84+
}
4685

4786
public VariableRenamer(CompilationUnit unit) {
4887
super(unit);
@@ -102,11 +141,28 @@ private void pushType(TypeElement type) {
102141
for (VariableElement field : fields) {
103142
fullFieldNames.add(nameTable.getVariableShortName(field));
104143
}
105-
fieldNameStack.add(fullFieldNames);
144+
fieldNameStack.push(fullFieldNames);
145+
// A nested type defines a new nested scope. The used names from the enclosing scope are kept
146+
// since the nested type can reference variables from the enclosing scope.
147+
Scope newScope = enterVariableScope();
148+
// Mark all the field names as unavailable for renaming.
149+
newScope.usedNames.addAll(fullFieldNames);
106150
}
107151

108152
private void popType() {
109-
fieldNameStack.remove(fieldNameStack.size() - 1);
153+
fieldNameStack.pop();
154+
scopes.pop();
155+
}
156+
157+
@CanIgnoreReturnValue
158+
private Scope enterVariableScope() {
159+
Scope newScope = new Scope(scopes.peek());
160+
scopes.push(newScope);
161+
return newScope;
162+
}
163+
164+
private void exitVariableScope() {
165+
scopes.pop();
110166
}
111167

112168
@Override
@@ -119,13 +175,8 @@ public void endVisit(SimpleName node) {
119175
// Make sure fields for the declaring type are renamed.
120176
collectAndRenameFields(ElementUtil.getDeclaringClass(var), new HashSet<VariableElement>());
121177
} else {
122-
// Local variable or parameter. Rename if it shares a name with a field.
123-
String varName = ElementUtil.getName(var);
124-
assert fieldNameStack.size() > 0;
125-
Set<String> fieldNames = fieldNameStack.get(fieldNameStack.size() - 1);
126-
if (fieldNames.contains(varName)) {
127-
nameTable.setVariableName(var, varName + "Arg");
128-
}
178+
// Local variable or parameter.
179+
handleVariable(var);
129180
}
130181
}
131182

@@ -183,4 +234,98 @@ public boolean visit(RecordDeclaration node) {
183234
public void endVisit(RecordDeclaration node) {
184235
popType();
185236
}
237+
238+
@Override
239+
public boolean visit(MethodDeclaration node) {
240+
enterVariableScope();
241+
return true;
242+
}
243+
244+
@Override
245+
public void endVisit(MethodDeclaration node) {
246+
exitVariableScope();
247+
}
248+
249+
@Override
250+
public boolean visit(Block node) {
251+
enterVariableScope();
252+
return true;
253+
}
254+
255+
@Override
256+
public void endVisit(Block node) {
257+
exitVariableScope();
258+
}
259+
260+
@Override
261+
public boolean visit(ForStatement node) {
262+
enterVariableScope();
263+
return true;
264+
}
265+
266+
@Override
267+
public void endVisit(ForStatement node) {
268+
exitVariableScope();
269+
}
270+
271+
@Override
272+
public boolean visit(EnhancedForStatement node) {
273+
enterVariableScope();
274+
return true;
275+
}
276+
277+
@Override
278+
public void endVisit(EnhancedForStatement node) {
279+
exitVariableScope();
280+
}
281+
282+
@Override
283+
public boolean visit(CatchClause node) {
284+
enterVariableScope();
285+
return true;
286+
}
287+
288+
@Override
289+
public void endVisit(CatchClause node) {
290+
exitVariableScope();
291+
}
292+
293+
@Override
294+
public boolean visit(SingleVariableDeclaration variableDeclaration) {
295+
handleVariable(variableDeclaration.getVariableElement());
296+
return true;
297+
}
298+
299+
@Override
300+
public boolean visit(VariableDeclarationFragment variableDeclaration) {
301+
handleVariable(variableDeclaration.getVariableElement());
302+
return true;
303+
}
304+
305+
private void handleVariable(VariableElement variable) {
306+
if (variable.getKind().isField()) {
307+
return;
308+
}
309+
Scope scope = scopes.peek();
310+
if (scope.needsRenaming(variable)) {
311+
// Variable needs renaming. Preserve the logic that renames parameters with "Arg" suffix.
312+
String baseName = ElementUtil.getName(variable) + (isParameter(variable) ? "Arg" : "");
313+
String suffix = "";
314+
int count = 1;
315+
while (scope.usedNames.contains(baseName + suffix)) {
316+
suffix = "_" + count++;
317+
}
318+
String newName = baseName + suffix;
319+
nameTable.setVariableName(variable, newName);
320+
scope.usedNames.add(newName);
321+
}
322+
}
323+
324+
private boolean isParameter(VariableElement variable) {
325+
Element element = variable.getEnclosingElement();
326+
if (element instanceof ExecutableElement executableElement) {
327+
return executableElement.getParameters().contains(variable);
328+
}
329+
return false;
330+
}
186331
}

translator/src/test/java/com/google/devtools/j2objc/gen/StatementGeneratorTest.java

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1996,10 +1996,10 @@ public void testInstanceOfPatternVariableTranslation() throws IOException {
19961996
"Test.m");
19971997
assertTranslatedLines(
19981998
translation,
1999-
"NSString *s$pattern$0;",
2000-
"if ((s$pattern$0 = [o isKindOfClass:[NSString class]] ? (NSString *) o"
2001-
+ " : nil, !JreStringEqualsEquals(s$pattern$0, nil))) {",
2002-
"return [s$pattern$0 java_length];",
1999+
"NSString *s;",
2000+
"if ((s = [o isKindOfClass:[NSString class]] ? (NSString *) o"
2001+
+ " : nil, !JreStringEqualsEquals(s, nil))) {",
2002+
"return [s java_length];",
20032003
"}",
20042004
"return 0;");
20052005
}
@@ -2027,10 +2027,10 @@ public void testInstanceOfPatternVariableTranslationWithGuards() throws IOExcept
20272027
"Test.m");
20282028
assertTranslatedLines(
20292029
translation,
2030-
"Point *p$pattern$0;",
2031-
"if ((p$pattern$0 = [o isKindOfClass:[Point class]] ? (Point *) o : nil,"
2032-
+ " !JreObjectEqualsEquals(p$pattern$0, nil)) && x_ == p$pattern$0->x_ && y_ =="
2033-
+ " p$pattern$0->y_) {",
2030+
"Point *p;",
2031+
"if ((p = [o isKindOfClass:[Point class]] ? (Point *) o : nil,"
2032+
+ " !JreObjectEqualsEquals(p, nil)) && x_ == p->x_ && y_ =="
2033+
+ " p->y_) {",
20342034
" return true;",
20352035
"}",
20362036
"else {",

translator/src/test/java/com/google/devtools/j2objc/translate/InnerClassExtractorTest.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -792,8 +792,9 @@ public void testInnerClassWithVarargsAndCaptureVariables() throws IOException {
792792
assertTranslation(translation,
793793
"create_Test_1Inner_initWithInt_withNSObjectArray_(i, "
794794
+ "[IOSObjectArray arrayWithObjects:(id[]){ o } count:1 type:NSObject_class_()])");
795-
assertTranslation(translation,
795+
assertTranslation(
796+
translation,
796797
"void Test_1Inner_initWithInt_withNSObjectArray_("
797-
+ "Test_1Inner *self, int32_t capture$0, IOSObjectArray *o)");
798+
+ "Test_1Inner *self, int32_t capture$0, IOSObjectArray *oArg)");
798799
}
799800
}

0 commit comments

Comments
 (0)