/* * 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 java.io.IOException; /** * Tests for {@link NilCheckResolver}. * * @author Keith Stanger */ public class NilCheckResolverTest extends GenerationTest { public void testNilCheckArrayLength() throws IOException { String translation = translateSourceFile( "public class A { int length(char[] s) { return s.length; } void test() { length(null);} }", "A", "A.m"); assertTranslation(translation, "return ((IOSCharArray *) nil_chk(s))->size_;"); } public void testNilCheckOnCastExpression() throws IOException { String translation = translateSourceFile( "class Test { int i; void test(Object o) { int i = ((Test) o).i; } }", "Test", "Test.m"); assertTranslation(translation, "((Test *) nil_chk(((Test *) cast_chk(o, [Test class]))))->i_"); } public void testNoNilCheckOnSecondDereference() throws IOException { String translation = translateSourceFile( "class Test { void test(Object o) { o.toString(); o.toString(); } }", "Test", "Test.m"); assertTranslatedLines(translation, "[nil_chk(o) description];", "[o description];"); } public void testNilCheckAfterReassignment() throws IOException { String translation = translateSourceFile( "class Test { void test(Object o, boolean b) { o.toString(); if (b) { o = null; } " + "o.toString(); o = new Object(); o.toString(); } }", "Test", "Test.m"); assertTranslatedLines(translation, "nil_chk(o) description];", "if (b) {", " o = nil;", "}", "[nil_chk(o) description];", "o = create_NSObject_init();", "[o description];"); } public void testNilCheckAfterIfStatement() throws IOException { String translation = translateSourceFile( "class Test { void test(Object o, boolean b) { if (b) { o.toString(); } o.toString(); } }", "Test", "Test.m"); assertTranslatedLines(translation, "if (b) {", "[nil_chk(o) description];", "}", "[nil_chk(o) description];"); } public void testEqualsNullTest() throws IOException { String translation = translateSourceFile( "class Test { " + "void test1(Object o1) { if (o1 == null) { o1.toString(); } else { o1.toString(); } }" + "void test2(Object o2) { if (o2 != null) { o2.toString(); } else { o2.toString(); } }" + "void test3(Object o3, boolean b) { " + "if (o3 != null || b) { o3.toString(); } else { o3.toString(); } } }", "Test", "Test.m"); assertTranslatedLines(translation, "if (o1 == nil) {", "[nil_chk(o1) description];", "}", "else {", "[o1 description];", "}"); assertTranslatedLines(translation, "if (o2 != nil) {", "[o2 description];", "}", "else {", "[nil_chk(o2) description];", "}"); assertTranslatedLines(translation, "if (o3 != nil || b) {", "[nil_chk(o3) description];", "}", "else {", "[nil_chk(o3) description];", "}"); } public void testNilCheckAfterWhileLoop() throws IOException { String translation = translateSourceFile( "class Test { void test(Object o, boolean b) { " + "while (b) { o.toString(); } o.toString(); } }", "Test", "Test.m"); assertTranslatedLines(translation, "while (b) {", "[nil_chk(o) description];", "}", "[nil_chk(o) description];"); } public void testNilCheckAfterConditionalInfix() throws IOException { String translation = translateSourceFile( "abstract class Test { abstract boolean foo(); void test(Test t1, Test t2, boolean b) { " + "boolean b1 = b && t1.foo(); t1.foo(); boolean b2 = b || t2.foo(); t2.foo(); } }", "Test", "Test.m"); assertTranslatedLines(translation, "jboolean b1 = b && [((Test *) nil_chk(t1)) foo];", "[((Test *) nil_chk(t1)) foo];", "jboolean b2 = b || [((Test *) nil_chk(t2)) foo];", "[((Test *) nil_chk(t2)) foo];"); } public void testNilCheckEnhancedForExpression() throws IOException { String translation = translateSourceFile( "public class Test {" + " boolean test(String target, java.util.List<String> strings) {" + " for (String s : strings) {" + " if (s.equals(target)) return true;" + " }" + " return false;" + " }}", "Test", "Test.m"); assertTranslation(translation, "nil_chk(strings)"); } // Method invocations can have side-effects public void testFieldsNeedCheckAfterInvocation() throws IOException { String translation = translateSourceFile( "public class Test { class Foo { int i; } Foo foo;" + " void test() { int i; i = foo.i; i = foo.i; System.gc(); i = foo.i; super.toString();" + " i = foo.i; new Test(); i = foo.i; } }", "Test", "Test.m"); assertTranslatedLines(translation, "i = ((Test_Foo *) nil_chk(foo_))->i_;", "i = foo_->i_;", "JavaLangSystem_gc();", "i = ((Test_Foo *) nil_chk(foo_))->i_;", "[super description];", "i = ((Test_Foo *) nil_chk(foo_))->i_;", "create_Test_init();", "i = ((Test_Foo *) nil_chk(foo_))->i_;"); } public void testReassignedVariableInLoop() throws IOException { String translation = translateSourceFile( "public abstract class Test { abstract boolean getB();" + " void test(Object o, Iterable<Integer> ints) {" + " o.toString(); do { o.toString(); o = null; } while(getB());" + " o.toString(); while(getB()) { o.toString(); o = null; }" + " o.toString(); for(; getB();) { o.toString(); o = null; }" + " o.toString(); for(Integer i : ints) { o.toString(); o = null; } } }", "Test", "Test.m"); assertTranslatedLines(translation, "[nil_chk(o) description];", "do {", " [nil_chk(o) description];", " o = nil;", "}", "while ([self getB]);", "[nil_chk(o) description];", "while ([self getB]) {", " [nil_chk(o) description];", " o = nil;", "}", "[nil_chk(o) description];", "for (; [self getB]; ) {", " [nil_chk(o) description];", " o = nil;", "}", "[nil_chk(o) description];", "for (JavaLangInteger * __strong i in nil_chk(ints)) {", " [nil_chk(o) description];", " o = nil;", "}"); } public void testBreakAndContinue() throws IOException { String translation = translateSourceFile( "public class Test { void test(Object o, boolean b, boolean b2) { o.toString();" + " dummy: do { o.toString(); if (b) { o = null; break; } if (b2) { o = null; continue; }" + " o.toString(); } while(Math.random() < 0.5); o.toString(); } }", "Test", "Test.m"); assertTranslatedLines(translation, "[nil_chk(o) description];", "dummy: do {", " [nil_chk(o) description];", // Needs nil_chk because of continue statement. " if (b) {", " o = nil;", " break;", " }", " if (b2) {", " o = nil;", " continue;", " }", // No nil_chk because of reassignments of "o" are followed by control // flow breaking statements. " [o description];", "}", "while (JavaLangMath_random() < 0.5);", "[nil_chk(o) description];"); // Needs nil_chk because of break statement. } public void testTryCatch() throws IOException { String translation = translateSourceFile( "public class Test { class Foo { int i; } void test(Foo f) { int i; i = f.i; try {" + " f = null; System.gc(); i = f.i; } catch (Throwable t) { i = f.i; }" + " finally { i = f.i; } i = f.i; } }", "Test", "Test.m"); assertTranslatedLines(translation, "i = ((Test_Foo *) nil_chk(f))->i_;", "@try {", " f = nil;", " JavaLangSystem_gc();", " i = ((Test_Foo *) nil_chk(f))->i_;", "}", "@catch (NSException *t) {", " i = ((Test_Foo *) nil_chk(f))->i_;", "}", "@finally {", " i = ((Test_Foo *) nil_chk(f))->i_;", "}", "i = f->i_;"); } public void testScopeTermination() throws IOException { String translation = translateSourceFile( "public class Test { void test(Object o1, Object o2, boolean b) {" + " if (o1 == null) { return; } o1.toString();" + " if (o2 != null) System.gc(); else if (b) throw new RuntimeException(); else return;" + " o2.toString(); } }", "Test", "Test.m"); assertTranslatedLines(translation, "if (o1 == nil) {", " return;", "}", "[o1 description];", "if (o2 != nil) JavaLangSystem_gc();", "else if (b) @throw create_JavaLangRuntimeException_init();", "else return;", "[o2 description];"); } public void testTrickyEqualsNullChecks() throws IOException { // Try to trick the translator into thinking that "o" is safe. String translation = translateSourceFile( "class Test { boolean foo(boolean b) { return true; } void test(Object o) {" + " if (o != null ? false : true) { o.toString(); }" + " if (foo(o != null)) { o.toString(); } } }", "Test", "Test.m"); assertTranslatedLines(translation, "if (o != nil ? false : true) {", " [nil_chk(o) description];", "}", "if ([self fooWithBoolean:o != nil]) {", " [nil_chk(o) description];", "}"); } public void testNullCheckInLoopContition() throws IOException { String translation = translateSourceFile( "abstract class Test { abstract Object getObj(); void test(Object o) {" + " while (o == null) { o = getObj(); } o.toString();" + " for (; o != null; ) { o.toString(); o = getObj(); } o.toString(); } }", "Test", "Test.m"); assertTranslatedLines(translation, "while (o == nil) {", " o = [self getObj];", "}", "[o description];", "for (; o != nil; ) {", " [o description];", " o = [self getObj];", "}", "[nil_chk(o) description];"); } public void testSwitchStatement() throws IOException { String translation = translateSourceFile( "class Test { void test(Object o1, Object o2, int i) { o2.toString(); switch(i) {" + " case 1: o1.toString(); case 2: o1.toString(); break;" + " case 3: o2 = null; case 4: o2.toString(); } } }", "Test", "Test.m"); assertTranslatedLines(translation, "[nil_chk(o2) description];", "switch (i) {", " case 1:", " [nil_chk(o1) description];", " case 2:", " [nil_chk(o1) description];", " break;", " case 3:", " o2 = nil;", " case 4:", " [nil_chk(o2) description];", "}"); } // Volatile fields should always nil_chk'ed. public void testVolatileField() throws IOException { String translation = translateSourceFile( "class Test { static class Foo { int i; } volatile Foo f;" + " void test() { int i; i = f.i; i = f.i; } }", "Test", "Test.m"); assertTranslatedLines(translation, "i = ((Test_Foo *) nil_chk(JreLoadVolatileId(&f_)))->i_;", "i = ((Test_Foo *) nil_chk(JreLoadVolatileId(&f_)))->i_;"); } public void testLabeledBlock() throws IOException { String translation = translateSourceFile( "class Test { void test(Object o, boolean b) { test_label: { o.toString();" + " if (b) { o = null; break test_label; } o.toString(); } o.toString(); } }", "Test", "Test.m"); assertTranslatedLines(translation, "{", " [nil_chk(o) description];", " if (b) {", " o = nil;", " goto break_test_label;", " }", " [o description];", "}", "break_test_label: ;", "[nil_chk(o) description];"); } public void testOuterExpressionOfClassInstanceCreation() throws IOException { String translation = translateSourceFile( "class Test { class Inner {} public Inner test(Test t) { return t.new Inner(); } }", "Test", "Test.m"); // "t" needs a nil_chk. assertTranslation(translation, "return create_Test_Inner_initWithTest_(nil_chk(t));"); } // Verify "throw null" throws a null pointer exception. JLS 14.18: // "If [the throws expression produces] a null value, then an instance of // class NullPointerException is created and thrown instead of null." public void testThrowNull() throws IOException { String translation = translateSourceFile( "class Test { " + "void test1() { throw null; }" + "void test2(RuntimeException e) { throw e; }" + "void test3() { throw new RuntimeException(); }}", "Test", "Test.m"); assertTranslation(translation, "@throw nil_chk(nil);"); assertTranslation(translation, "@throw nil_chk(e);"); assertTranslation(translation, "@throw create_JavaLangRuntimeException_init();"); } }