/* * 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 com.google.devtools.j2objc.translate; import com.google.devtools.j2objc.GenerationTest; import com.google.devtools.j2objc.Options.MemoryManagementOption; import com.google.devtools.j2objc.ast.Statement; import java.io.IOException; import java.util.List; /** * Unit tests for {@link OperatorRewriter}. * * @author Keith Stanger */ public class OperatorRewriterTest extends GenerationTest { public void testSetFieldOnResultOfExpression() throws IOException { String translation = translateSourceFile( "class Test { String s; static Test getTest() { return null; } " + "void test(boolean b) { (b ? new Test() : getTest()).s = \"foo\"; } }", "Test", "Test.m"); assertTranslation(translation, "JreStrongAssign(&(b ? create_Test_init() : Test_getTest())->s_, @\"foo\");"); } public void testModAssignOperator() throws IOException { String source = "float a = 4.2f; a %= 2.1f; double b = 5.6; b %= 1.2; byte c = 3; c %= 2.3; " + "short d = 4; d %= 3.4; int e = 5; e %= 4.5; long f = 6; f %= 5.6; char g = 'a'; " + "g %= 6.7;"; List<Statement> stmts = translateStatements(source); assertEquals(14, stmts.size()); assertEquals("JreModAssignFloatF(&a, 2.1f);", generateStatement(stmts.get(1))); assertEquals("JreModAssignDoubleD(&b, 1.2);", generateStatement(stmts.get(3))); assertEquals("JreModAssignByteD(&c, 2.3);", generateStatement(stmts.get(5))); assertEquals("JreModAssignShortD(&d, 3.4);", generateStatement(stmts.get(7))); assertEquals("JreModAssignIntD(&e, 4.5);", generateStatement(stmts.get(9))); assertEquals("JreModAssignLongD(&f, 5.6);", generateStatement(stmts.get(11))); assertEquals("JreModAssignCharD(&g, 6.7);", generateStatement(stmts.get(13))); } public void testDoubleModulo() throws IOException { String translation = translateSourceFile( "public class A { " + " double doubleMod(double one, double two) { return one % two; }" + " float floatMod(float three, float four) { return three % four; }}", "A", "A.m"); assertTranslation(translation, "return fmod(one, two);"); assertTranslation(translation, "return fmodf(three, four);"); } public void testLShift32WithExtendedOperands() throws IOException { String source = "int a; a = 1 << 2; a = 1 << 2 << 3; a = 1 << 2 << 3 << 4;"; List<Statement> stmts = translateStatements(source); assertEquals(4, stmts.size()); assertEquals("a = JreLShift32(1, 2);", generateStatement(stmts.get(1))); assertEquals("a = JreLShift32(JreLShift32(1, 2), 3);", generateStatement(stmts.get(2))); assertEquals("a = JreLShift32(JreLShift32(JreLShift32(1, 2), 3), 4);", generateStatement(stmts.get(3))); } public void testURShift64WithExtendedOperands() throws IOException { String source = "long a; a = 65535L >>> 2; a = 65535L >>> 2 >>> 3; " + "a = 65535L >>> 2 >>> 3 >>> 4;"; List<Statement> stmts = translateStatements(source); assertEquals(4, stmts.size()); assertEquals("a = JreURShift64(65535LL, 2);", generateStatement(stmts.get(1))); assertEquals("a = JreURShift64(JreURShift64(65535LL, 2), 3);", generateStatement(stmts.get(2))); assertEquals("a = JreURShift64(JreURShift64(JreURShift64(65535LL, 2), 3), 4);", generateStatement(stmts.get(3))); } public void testStringAppendOperator() throws IOException { String translation = translateSourceFile( "import com.google.j2objc.annotations.Weak;" + " class Test { String ss; @Weak String ws; String[] as;" + " void test() { ss += \"foo\"; ws += \"bar\"; as[0] += \"baz\"; } }", "Test", "Test.m"); assertTranslatedLines(translation, "JreStrAppendStrong(&ss_, \"$\", @\"foo\");", "JreStrAppend(&ws_, \"$\", @\"bar\");", "JreStrAppendArray(IOSObjectArray_GetRef(nil_chk(as_), 0), \"$\", @\"baz\");"); } public void testParenthesizedLeftHandSide() throws IOException { String translation = translateSourceFile( "class Test { String s; void test(String s2) { (s) = s2; } }", "Test", "Test.m"); assertTranslation(translation, "JreStrongAssign(&(s_), s2);"); } public void testVolatileLoadAndAssign() throws IOException { String translation = translateSourceFile( "import com.google.j2objc.annotations.Weak;" + " class Test { volatile int i; static volatile int si; volatile String s;" + " static volatile String vs; @Weak volatile String ws;" + " void test() { int li = i; i = 2; li = si; si = 3; String ls = s; s = \"foo\";" + " ls = vs; vs = \"foo\"; ls = ws; ws = \"foo\"; } }", "Test", "Test.m"); assertTranslatedLines(translation, "jint li = JreLoadVolatileInt(&i_);", "JreAssignVolatileInt(&i_, 2);", "li = JreLoadVolatileInt(&Test_si);", "JreAssignVolatileInt(&Test_si, 3);", "NSString *ls = JreLoadVolatileId(&s_);", "JreVolatileStrongAssign(&s_, @\"foo\");", "ls = JreLoadVolatileId(&Test_vs);", "JreVolatileStrongAssign(&Test_vs, @\"foo\");", "ls = JreLoadVolatileId(&ws_);", "JreAssignVolatileId(&ws_, @\"foo\");"); } public void testPromotionTypesForCompundAssign() throws IOException { String translation = translateSourceFile( "class Test { volatile short s; int i; void test() {" + " s += 1; s -= 2l; s *= 3.0f; s /= 4.0; s %= 5l; i %= 6.0; } }", "Test", "Test.m"); assertTranslatedLines(translation, "JrePlusAssignVolatileShortI(&s_, 1);", "JreMinusAssignVolatileShortJ(&s_, 2l);", "JreTimesAssignVolatileShortF(&s_, 3.0f);", "JreDivideAssignVolatileShortD(&s_, 4.0);", "JreModAssignVolatileShortJ(&s_, 5l);", "JreModAssignIntD(&i_, 6.0);"); } public void testStringAppendLocalVariableARC() throws IOException { options.setMemoryManagementOption(MemoryManagementOption.ARC); String translation = translateSourceFile( "class Test { void test() { String str = \"foo\"; str += \"bar\"; } }", "Test", "Test.m"); // Local variables in ARC have strong semantics. assertTranslation(translation, "JreStrAppendStrong(&str, \"$\", @\"bar\")"); } public void testStringAppendInfixExpression() throws IOException { String translation = translateSourceFile( "class Test { void test(int x, int y) { " + "String str = \"foo\"; str += x + y; } }", "Test", "Test.m"); assertTranslation(translation, "JreStrAppend(&str, \"I\", x + y);"); translation = translateSourceFile( "class Test { void test(int x) { " + "String str = \"foo\"; str += \"bar\" + x; } }", "Test", "Test.m"); assertTranslation(translation, "JreStrAppend(&str, \"$I\", @\"bar\", x);"); } public void testRetainedWithAnnotation() throws IOException { String translation = translateSourceFile( "import com.google.j2objc.annotations.RetainedWith;" + "class Test { @RetainedWith Object rwo; @RetainedWith volatile Object rwvo;" + "Test getTest() { return new Test(); }" + "void test() { rwo = new Object(); rwvo = new Object(); }" + "void test2() { getTest().rwo = new Object(); } }", "Test", "Test.m"); assertTranslation(translation, "JreRetainedWithAssign(self, &rwo_, create_NSObject_init());"); assertTranslation(translation, "JreVolatileRetainedWithAssign(self, &rwvo_, create_NSObject_init());"); assertTranslatedLines(translation, // The getTest() call must be extracted so that it can be passed as the parent ref without // duplicating the expression. "t *__rw$0;", "(__rw$0 = nil_chk([self getTest]), " + "JreRetainedWithAssign(__rw$0, &__rw$0->rwo_, create_NSObject_init()));"); // Test the dealloc calls too. assertTranslation(translation, "JreRetainedWithRelease(self, rwo_);"); assertTranslation(translation, "JreVolatileRetainedWithRelease(self, &rwvo_);"); } public void testRetainedLocalRef() throws IOException { String translation = translateSourceFile( "class Test { " + " boolean test1(String s1, String s2) {" + " @com.google.j2objc.annotations.RetainedLocalRef" + " java.util.Comparator<String> c = String.CASE_INSENSITIVE_ORDER;" + " return c.compare(s1, s2) == 0;" + " } " + " boolean test2(Thing t, Thing t2, String s1, String s2) {" + " @com.google.j2objc.annotations.RetainedLocalRef" + " Thing thing = t;" + " thing = t2;" + " return thing.comp.compare(s1, s2) == 0;" + " }" + " private static class Thing { public java.util.Comparator<String> comp; }}", "Test", "Test.m"); assertNotInTranslation(translation, "RetainedLocalRef"); assertTranslatedLines(translation, "id<JavaUtilComparator> c = JreRetainedLocalValue(JreLoadStatic(" + "NSString, CASE_INSENSITIVE_ORDER));", "return [((id<JavaUtilComparator>) nil_chk(c)) compareWithId:s1 withId:s2] == 0;"); assertTranslatedLines(translation, "Test_Thing *thing = JreRetainedLocalValue(t);", "thing = JreRetainedLocalValue(t2);", "return [((id<JavaUtilComparator>) nil_chk(((Test_Thing *) nil_chk(thing))->comp_)) " + "compareWithId:s1 withId:s2] == 0;"); } public void testLazyInitFields() throws IOException { addSourcesToSourcepaths(); addSourceFile("package com.google.errorprone.annotations.concurrent;" + "public @interface LazyInit {}", "com/google/errorprone/annotations/concurrent/LazyInit.java"); String translation = translateSourceFile( "import com.google.errorprone.annotations.concurrent.LazyInit;" + "class Test { @LazyInit String lazyStr; @LazyInit static String lazyStaticStr; }", "Test", "Test.h"); assertTranslation(translation, "volatile_id lazyStr_;"); assertTranslatedLines(translation, "FOUNDATION_EXPORT volatile_id Test_lazyStaticStr;", "J2OBJC_STATIC_FIELD_OBJ_VOLATILE(Test, lazyStaticStr, NSString *)"); } }