11package org .benf .cfr .reader .bytecode .analysis .opgraph .op4rewriters .transformers ;
22
3+ import org .benf .cfr .reader .bytecode .analysis .loc .BytecodeLoc ;
34import org .benf .cfr .reader .bytecode .analysis .opgraph .Op04StructuredStatement ;
4- import org .benf .cfr .reader .bytecode .analysis .opgraph .op4rewriters .ExpressionReplacingRewriter ;
55import org .benf .cfr .reader .bytecode .analysis .parse .Expression ;
66import org .benf .cfr .reader .bytecode .analysis .parse .LValue ;
7+ import org .benf .cfr .reader .bytecode .analysis .parse .expression .AssignmentExpression ;
78import org .benf .cfr .reader .bytecode .analysis .parse .expression .BoolOp ;
89import org .benf .cfr .reader .bytecode .analysis .parse .expression .BooleanExpression ;
910import org .benf .cfr .reader .bytecode .analysis .parse .expression .BooleanOperation ;
1011import org .benf .cfr .reader .bytecode .analysis .parse .expression .CastExpression ;
12+ import org .benf .cfr .reader .bytecode .analysis .parse .expression .CompOp ;
13+ import org .benf .cfr .reader .bytecode .analysis .parse .expression .ComparisonOperation ;
1114import org .benf .cfr .reader .bytecode .analysis .parse .expression .ConditionalExpression ;
1215import org .benf .cfr .reader .bytecode .analysis .parse .expression .InstanceOfExpression ;
1316import org .benf .cfr .reader .bytecode .analysis .parse .expression .InstanceOfExpressionDefining ;
17+ import org .benf .cfr .reader .bytecode .analysis .parse .expression .Literal ;
18+ import org .benf .cfr .reader .bytecode .analysis .parse .expression .LValueExpression ;
19+ import org .benf .cfr .reader .bytecode .analysis .types .JavaTypeInstance ;
1420import org .benf .cfr .reader .bytecode .analysis .structured .StructuredScope ;
1521import org .benf .cfr .reader .bytecode .analysis .structured .StructuredStatement ;
1622import org .benf .cfr .reader .bytecode .analysis .structured .statement .Block ;
1723import org .benf .cfr .reader .bytecode .analysis .structured .statement .StructuredAssignment ;
1824import org .benf .cfr .reader .bytecode .analysis .structured .statement .StructuredIf ;
19- import org .benf .cfr .reader .bytecode .analysis .types .JavaTypeInstance ;
25+ import org .benf .cfr .reader .util .collections .ListFactory ;
26+ import org .benf .cfr .reader .util .collections .SetFactory ;
2027
28+ import java .util .Iterator ;
29+ import java .util .LinkedList ;
2130import java .util .List ;
31+ import java .util .Set ;
2232
2333/*
24- * Lift the j18+ post-pattern-matching shape:
25- *
26- * if (obj instanceof T) {
27- * T s = (T) obj;
28- * ...
29- * }
34+ * Cleanup pass for the j18+ instanceof pattern shape that op03's
35+ * condenseInstanceOfAssign and scope discovery's j16 lift produce together.
3036 *
31- * into the pattern form:
37+ * We absorb every cast-assign into its preceding if's condition with the
38+ * synthetic null-check trick:
3239 *
33- * if (obj instanceof T s) {
34- * ...
35- * }
40+ * if (!(o instanceof T)) goto E; \
41+ * T b = (T)o; > ==> if (!(o instanceof T && null != (b = (T)o))) goto E;
42+ * <continuation> / <continuation>
3643 *
37- * The j14/j16 contrived shape (cast+assign folded inside the condition via a
38- * self-comparison) is handled by InstanceOfAssignRewriter at scope-discovery
39- * time. j18+ emits the straightforward shape above instead, so the lift has
40- * to happen post-hoc here.
44+ * Then scope discovery, via InstanceOfAssignRewriter's MatchType.SIMPLE_J16,
45+ * tries to lift the bound variable into the instanceof:
46+ * - SUCCESS (b is a fresh local): condition becomes
47+ * instanceof T b && null != b
48+ * The null-check is now redundant — instanceof T b implies b != null —
49+ * but no earlier pass elides it.
50+ * - REFUSAL (b is method-scoped, used outside the if): condition stays
51+ * instanceof T && null != (b = (T)o)
52+ * The original pre-pattern code was nicer than this absorbed form.
4153 *
42- * Also completes the compound case set up by the op03 condense pass that
43- * turns `instanceof T s && cond` j21 bytecode into:
54+ * This pass walks each StructuredIf's top-level AND chain and:
4455 *
45- * if (obj instanceof T && cond) {
46- * s = (T) obj;
47- * ...
48- * }
56+ * (1) Drops `null != b` operands (in that exact order — the canary shape
57+ * this pipeline synthesises) when a sibling defines b via
58+ * `instanceof T b`.
59+ * (2) Pushes a *trailing* `null != (b = (T)o)` operand down into the body
60+ * as an assignment statement, when no sibling defines b. Only the LAST
61+ * operand qualifies — see correctness note below.
4962 *
50- * by collapsing the leading assignment into an instanceof-defining pattern.
63+ * Correctness note on the push-down:
64+ * Original: instanceof T && null != (b = (T)o) && cond2
65+ * - instanceof T true, cond2 false → the && evaluates left-to-right, the
66+ * assignment side-effect of the middle clause runs BEFORE cond2 is
67+ * checked, so b IS assigned even when the if isn't taken.
68+ * Naive push-down to: if (instanceof T && cond2) { b = (T)o; ... }
69+ * - cond2 false → body not entered → b NOT assigned. Differs.
70+ * For a method-scoped b that's read after the if, this is observable.
71+ * Therefore push the assignment down only if it is the LAST operand, where
72+ * no later condition can short-circuit and the side effect would have been
73+ * observable only on the if-taken path anyway.
5174 */
5275public class InstanceOfMatchCheckTransformer implements StructuredStatementTransformer {
5376
@@ -60,77 +83,170 @@ public void transform(Op04StructuredStatement root) {
6083 public StructuredStatement transform (StructuredStatement in , StructuredScope scope ) {
6184 in .transformStructuredChildren (this , scope );
6285 if (in instanceof StructuredIf ) {
63- tryLift ((StructuredIf ) in );
86+ tidy ((StructuredIf ) in );
6487 }
6588 return in ;
6689 }
6790
68- private static void tryLift (StructuredIf sif ) {
69- Op04StructuredStatement firstContainer = firstStatementOf (sif .getIfTaken ());
70- if (firstContainer == null ) return ;
71- StructuredStatement first = firstContainer .getStatement ();
72- if (!(first instanceof StructuredAssignment )) return ;
73- StructuredAssignment assign = (StructuredAssignment ) first ;
91+ private static void tidy (StructuredIf sif ) {
92+ ConditionalExpression cond = sif .getConditionalExpression ();
93+ List <ConditionalExpression > operands = ListFactory .newList ();
94+ flattenAnd (cond , operands );
95+ if (operands .size () <= 1 ) return ;
7496
75- // Lift only when the assignment IS the variable's creation point.
76- LValue lvalue = assign .getLvalue ();
77- if (!assign .isCreator (lvalue )) return ;
97+ Set <LValue > defined = SetFactory .newSet ();
98+ for (ConditionalExpression op : operands ) {
99+ LValue d = getDefinedLValue (op );
100+ if (d != null ) defined .add (d );
101+ }
78102
79- if (!(assign .getRvalue () instanceof CastExpression )) return ;
80- CastExpression cast = (CastExpression ) assign .getRvalue ();
81- Expression castSubject = cast .getChild ();
82- JavaTypeInstance castType = cast .getInferredJavaType ().getJavaTypeInstance ();
103+ boolean changed = false ;
104+ AssignmentExpression pushDown = null ;
83105
84- InstanceOfExpression target = findReachable (sif .getConditionalExpression (), castSubject , castType );
85- if (target == null ) return ;
106+ // (2) Push-down candidate: only the LAST operand, only if it's our
107+ // canary's full shape — `null != (b = (T)o)` paired with a plain
108+ // `instanceof T` sibling whose subject and type match the cast.
109+ // The sibling-match is what makes the rewrite semantics-preserving:
110+ // with `instanceof T` true on the if-taken path, `(T)o` is provably
111+ // non-null, so the null-check it carries is dead weight. Without
112+ // that pairing (e.g. a user-written `null != (b = somefunc())`), the
113+ // null-check is doing real work and we must NOT push it down — see
114+ // InstanceOfPatternTest20 for the negative case.
115+ ConditionalExpression last = operands .get (operands .size () - 1 );
116+ AssignmentExpression absorbed = getAbsorbedAssignment (last );
117+ if (absorbed != null
118+ && !defined .contains (absorbed .getlValue ())
119+ && hasMatchingInstanceofSibling (absorbed , operands )) {
120+ pushDown = absorbed ;
121+ operands .remove (operands .size () - 1 );
122+ changed = true ;
123+ }
86124
87- InstanceOfExpressionDefining replacement = new InstanceOfExpressionDefining (
88- target .getLoc (),
89- target .getInferredJavaType (),
90- target .getLhs (),
91- castType ,
92- lvalue
93- );
125+ // (1) Drop redundant null-checks of variables bound by a sibling defining instanceof.
126+ Iterator <ConditionalExpression > it = operands .iterator ();
127+ while (it .hasNext ()) {
128+ LValue v = getNullCheckedLValue (it .next ());
129+ if (v != null && defined .contains (v )) {
130+ it .remove ();
131+ changed = true ;
132+ }
133+ }
94134
95- sif .rewriteExpressions (new ExpressionReplacingRewriter (target , replacement ));
96- firstContainer .nopOut ();
97- }
135+ if (!changed ) return ;
136+ if (operands .isEmpty ()) return ; // defensive — shouldn't happen with well-formed input
98137
99- private static Op04StructuredStatement firstStatementOf (Op04StructuredStatement s ) {
100- StructuredStatement stmt = s .getStatement ();
101- if (stmt instanceof Block ) {
102- List <Op04StructuredStatement > bs = ((Block ) stmt ).getBlockStatements ();
103- if (bs .isEmpty ()) return null ;
104- return bs .get (0 );
138+ ConditionalExpression rebuilt = operands .get (0 );
139+ for (int i = 1 ; i < operands .size (); i ++) {
140+ rebuilt = new BooleanOperation (BytecodeLoc .NONE , rebuilt , operands .get (i ), BoolOp .AND );
141+ }
142+ sif .setConditionalExpression (rebuilt );
143+
144+ if (pushDown != null ) {
145+ prependAssignment (sif , pushDown );
105146 }
106- return s ;
107147 }
108148
109149 /*
110- * Find an InstanceOfExpression matching (subject, type) reachable from the
111- * top of `cond` purely via &&-chain (and BooleanExpression wrapping). An
112- * instanceof to the rhs of a disjunction, or inside a not doesn't positively assign
113- * the then branch (though a 'not' can assign the else branch (!))
150+ * Add the assignment to the start of the block inside the conditional.
114151 */
115- private static InstanceOfExpression findReachable (Expression cond , Expression subject , JavaTypeInstance type ) {
116- if (cond instanceof BooleanExpression ) {
117- return findReachable (((BooleanExpression ) cond ).getInner (), subject , type );
152+ private static void prependAssignment (StructuredIf sif , AssignmentExpression assign ) {
153+ StructuredAssignment newAssign = new StructuredAssignment (
154+ BytecodeLoc .NONE , assign .getlValue (), assign .getrValue (), false );
155+ Op04StructuredStatement newAssignContainer = new Op04StructuredStatement (newAssign );
156+
157+ Op04StructuredStatement ifTaken = sif .getIfTaken ();
158+ StructuredStatement body = ifTaken .getStatement ();
159+ if (body instanceof Block ) {
160+ ((Block ) body ).getBlockStatements ().add (0 , newAssignContainer );
161+ } else {
162+ LinkedList <Op04StructuredStatement > kids = new LinkedList <Op04StructuredStatement >();
163+ kids .add (newAssignContainer );
164+ kids .add (new Op04StructuredStatement (body ));
165+ ifTaken .replaceStatement (new Block (kids , true ));
118166 }
119- if (cond instanceof InstanceOfExpression ) {
120- InstanceOfExpression ioe = (InstanceOfExpression ) cond ;
121- if (ioe .getLhs ().equals (subject ) && ioe .getTypeInstance ().equals (type )) {
122- return ioe ;
123- }
124- return null ;
167+ }
168+
169+ // Todo : This feels like it should be utility elsewhere.
170+ private static void flattenAnd (ConditionalExpression e , List <ConditionalExpression > out ) {
171+ if (e instanceof BooleanOperation && ((BooleanOperation ) e ).getOp () == BoolOp .AND ) {
172+ BooleanOperation bo = (BooleanOperation ) e ;
173+ flattenAnd (bo .getLhs (), out );
174+ flattenAnd (bo .getRhs (), out );
175+ return ;
125176 }
126- if (cond instanceof BooleanOperation ) {
127- BooleanOperation bo = (BooleanOperation ) cond ;
128- if (bo .getOp () != BoolOp .AND ) return null ;
129- InstanceOfExpression r = findReachable (bo .getLhs (), subject , type );
130- if (r != null ) return r ;
131- return findReachable (bo .getRhs (), subject , type );
177+ out .add (e );
178+ }
179+
180+ /*
181+ * InstanceOfExpressionDefining extends AbstractExpression but does NOT
182+ * implement ConditionalExpression — it must be wrapped in BooleanExpression
183+ * to appear inside a boolean condition. Since `op` is typed as a
184+ * ConditionalExpression, the only way to see an InstanceOfExpressionDefining
185+ * here is through that wrap. ComparisonOperation by contrast already
186+ * implements ConditionalExpression directly, so the matchers below don't
187+ * need to unwrap.
188+ */
189+ private static LValue getDefinedLValue (ConditionalExpression op ) {
190+ if (!(op instanceof BooleanExpression )) return null ;
191+ Expression inner = ((BooleanExpression ) op ).getInner ();
192+ if (inner instanceof InstanceOfExpressionDefining ) {
193+ return ((InstanceOfExpressionDefining ) inner ).getDefines ();
132194 }
133195 return null ;
134196 }
135197
198+ /*
199+ * Recognise only the exact canary shape introduced by condenseInstanceOfAssign
200+ * (Literal.NULL on the LHS, target expression on the RHS, op NE).
201+ * Returns the RHS, or null if `op` is not the canary shape.
202+ */
203+ private static Expression getCanaryRhs (ConditionalExpression op ) {
204+ if (!(op instanceof ComparisonOperation )) return null ;
205+ ComparisonOperation cmp = (ComparisonOperation ) op ;
206+ if (cmp .getOp () != CompOp .NE ) return null ;
207+ if (!Literal .NULL .equals (cmp .getLhs ())) return null ;
208+ return cmp .getRhs ();
209+ }
210+
211+ /*
212+ * `null != b` — the post-j16-lift residue, redundant given a sibling
213+ * `instanceof T b` clause.
214+ */
215+ private static LValue getNullCheckedLValue (ConditionalExpression op ) {
216+ Expression rhs = getCanaryRhs (op );
217+ if (!(rhs instanceof LValueExpression )) return null ;
218+ return ((LValueExpression ) rhs ).getLValue ();
219+ }
220+
221+ /*
222+ * `null != (b = (T)o)` — the assignment still embedded in the canary
223+ * because the j16 lift refused (e.g. b is method-scoped).
224+ */
225+ private static AssignmentExpression getAbsorbedAssignment (ConditionalExpression op ) {
226+ Expression rhs = getCanaryRhs (op );
227+ return rhs instanceof AssignmentExpression ? (AssignmentExpression ) rhs : null ;
228+ }
229+
230+ /*
231+ * The canary always pairs the absorbed `null != (b = (T)o)` with a plain
232+ * `instanceof T` whose subject and type match the cast. We require that
233+ * pairing to be confident this clause came from condenseInstanceOfAssign
234+ * rather than user code where the null-check carries semantic weight.
235+ */
236+ private static boolean hasMatchingInstanceofSibling (AssignmentExpression absorbed , List <ConditionalExpression > operands ) {
237+ if (!(absorbed .getrValue () instanceof CastExpression )) return false ;
238+ CastExpression cast = (CastExpression ) absorbed .getrValue ();
239+ Expression castSubject = cast .getChild ();
240+ JavaTypeInstance castType = cast .getInferredJavaType ().getJavaTypeInstance ();
241+ for (ConditionalExpression op : operands ) {
242+ if (!(op instanceof BooleanExpression )) continue ;
243+ Expression inner = ((BooleanExpression ) op ).getInner ();
244+ if (!(inner instanceof InstanceOfExpression )) continue ;
245+ InstanceOfExpression ioe = (InstanceOfExpression ) inner ;
246+ if (!ioe .getLhs ().equals (castSubject )) continue ;
247+ if (!ioe .getTypeInstance ().equals (castType )) continue ;
248+ return true ;
249+ }
250+ return false ;
251+ }
136252}
0 commit comments