Make System.getProperty fail at compile time for undefined properties.

System.getProperty is designed to enable dead code stripping, so give an
early error if a property is used but not declared.

This patch also refactors the test infrastructure a bit to allow some
reused of CheckerTests by UnifyAstTest.

Change-Id: I7b24575f14e76ed74c32872bd9699155fa8c9cdd
diff --git a/dev/core/src/com/google/gwt/dev/javac/testing/impl/JavaResourceBase.java b/dev/core/src/com/google/gwt/dev/javac/testing/impl/JavaResourceBase.java
index 57138a9..db87a1d 100644
--- a/dev/core/src/com/google/gwt/dev/javac/testing/impl/JavaResourceBase.java
+++ b/dev/core/src/com/google/gwt/dev/javac/testing/impl/JavaResourceBase.java
@@ -345,6 +345,16 @@
           "  String[] value();",
           "}");
 
+  public static final MockJavaResource SYSTEM =
+      createMockJavaResource("java.lang.System",
+          "package java.lang;",
+          "public class System {",
+          "  public static String getProperty(String propertyName) { return null; }",
+          "  public static String getProperty(String propertyName, String defaultValue) {",
+          "    return defaultValue;",
+          "  }",
+          "}");
+
   public static final MockJavaResource THROWABLE =
       createMockJavaResource("java.lang.Throwable",
           "package java.lang;",
@@ -400,7 +410,7 @@
         CLASS_NOT_FOUND_EXCEPTION, CLONEABLE, COLLECTION, COMPARABLE, DOUBLE, ENUM, EXCEPTION,
         ERROR, FUNCTIONALINTERFACE, FLOAT, INTEGER, IS_SERIALIZABLE, JAVASCRIPTEXCEPTION,
         JAVASCRIPTOBJECT, LIST, LONG, MAP, NO_CLASS_DEF_FOUND_ERROR, NUMBER, OBJECT,
-        RUNTIME_EXCEPTION, SERIALIZABLE, SHORT, STRING, STRING_BUILDER, SUPPRESS_WARNINGS,
+        RUNTIME_EXCEPTION, SERIALIZABLE, SHORT, STRING, STRING_BUILDER, SUPPRESS_WARNINGS, SYSTEM,
         THROWABLE, SPECIALIZE_METHOD, JSTYPE, JSTYPEPROTOTYPE, JSEXPORT, JSPROPERTY, JSFUNCTION};
   }
 
diff --git a/dev/core/src/com/google/gwt/dev/jjs/AstConstructor.java b/dev/core/src/com/google/gwt/dev/jjs/AstConstructor.java
index 95a0c87..0531200 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/AstConstructor.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/AstConstructor.java
@@ -40,19 +40,25 @@
  */
 public class AstConstructor {
 
+  public static JProgram construct(TreeLogger logger, final CompilationState state,
+      PrecompileTaskOptions options, ConfigurationProperties config)
+      throws UnableToCompleteException {
+    CompilerContext compilerContext = new CompilerContext.Builder().options(options)
+        .minimalRebuildCache(new NullRebuildCache()).build();
+    return construct(logger, state, compilerContext, config);
+  }
+
   /**
    * Construct an simple AST representing an entire {@link CompilationState}.
    * Does not support deferred binding. Implementation mostly copied from
    * {@link JavaToJavaScriptCompiler}.
    */
   public static JProgram construct(TreeLogger logger, final CompilationState state,
-      PrecompileTaskOptions options, ConfigurationProperties config) throws UnableToCompleteException {
+      CompilerContext compilerContext, ConfigurationProperties config)
+      throws UnableToCompleteException {
 
     InternalCompilerException.preload();
 
-    CompilerContext compilerContext = new CompilerContext.Builder().options(options)
-        .minimalRebuildCache(new NullRebuildCache()).build();
-
     PrecompilationContext precompilationContext = new PrecompilationContext(
         new RebindPermutationOracle() {
           @Override
@@ -93,7 +99,7 @@
      * TODO: If we defer this until later, we could maybe use the results of the
      * assertions to enable more optimizations.
      */
-    if (options.isEnableAssertions()) {
+    if (compilerContext.getOptions().isEnableAssertions()) {
       // Turn into assertion checking calls.
       AssertionNormalizer.exec(jprogram);
     } else {
@@ -101,7 +107,7 @@
       AssertionRemover.exec(jprogram);
     }
 
-    if (options.isRunAsyncEnabled()) {
+    if (compilerContext.getOptions().isRunAsyncEnabled()) {
       ReplaceRunAsyncs.exec(logger, jprogram);
       if (config != null) {
         CodeSplitters.pickInitialLoadSequence(logger, jprogram, config);
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/UnifyAst.java b/dev/core/src/com/google/gwt/dev/jjs/impl/UnifyAst.java
index 3a939d9..88f4f93 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/UnifyAst.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/UnifyAst.java
@@ -21,6 +21,8 @@
 import com.google.gwt.dev.CompilerContext;
 import com.google.gwt.dev.MinimalRebuildCache;
 import com.google.gwt.dev.Permutation;
+import com.google.gwt.dev.cfg.ConfigurationProperty;
+import com.google.gwt.dev.cfg.Property;
 import com.google.gwt.dev.javac.CompilationProblemReporter;
 import com.google.gwt.dev.javac.CompilationState;
 import com.google.gwt.dev.javac.CompilationUnit;
@@ -413,22 +415,30 @@
     private JExpression handleSystemGetProperty(JMethodCall gwtGetPropertyCall) {
       assert (gwtGetPropertyCall.getArgs().size() == 1 || gwtGetPropertyCall.getArgs().size() == 2);
       JExpression propertyNameExpression = gwtGetPropertyCall.getArgs().get(0);
-      JExpression defaultValueExpression = gwtGetPropertyCall.getArgs().size() == 2 ?
+      boolean defaultVersionCalled = gwtGetPropertyCall.getArgs().size() == 2;
+      JExpression defaultValueExpression = defaultVersionCalled ?
           gwtGetPropertyCall.getArgs().get(1) : null;
 
       if (!(propertyNameExpression instanceof JStringLiteral) ||
-          (defaultValueExpression != null && !(defaultValueExpression instanceof JStringLiteral))) {
+          (defaultVersionCalled && !(defaultValueExpression instanceof JStringLiteral))) {
         error(gwtGetPropertyCall,
             "Only string constants may be used as arguments to System.getProperty()");
         return null;
       }
       String propertyName = ((JStringLiteral) propertyNameExpression).getValue();
 
-      if (isMultivaluedProperty(propertyName)) {
-        error(gwtGetPropertyCall,
-            "Multivalued properties are not supported by System.getProperty()");
+      if (!defaultVersionCalled && !isPropertyDefined(propertyName)) {
+        error(gwtGetPropertyCall, "Property '" + propertyName + "' is not defined.");
         return null;
       }
+
+      if (isMultivaluedProperty(propertyName)) {
+        error(gwtGetPropertyCall,
+            "Property '" + propertyName + "' is multivalued. " +
+                "Multivalued properties are not supported by System.getProperty().");
+        return null;
+      }
+
       String defaultValue = defaultValueExpression == null ? null :
           ((JStringLiteral) defaultValueExpression).getValue();
       return JPermutationDependentValue
@@ -591,9 +601,16 @@
   }
 
   private boolean isMultivaluedProperty(String propertyName) {
-    // Multivalued properties can only be Configuration properties, and those do not change between
-    // permutations.
-    return permutations[0].getProperties().getConfigurationProperties().isMultiValued(propertyName);
+    Property property = compilerContext.getModule().getProperties().find(propertyName);
+    if (!(property instanceof ConfigurationProperty)) {
+      return false;
+    }
+
+    return ((ConfigurationProperty) property).allowsMultipleValues();
+  }
+
+  private boolean isPropertyDefined(String propertyName) {
+    return compilerContext.getModule().getProperties().find(propertyName) != null;
   }
 
   private static final String CLASS_DESIRED_ASSERTION_STATUS =
diff --git a/dev/core/test/com/google/gwt/dev/cfg/MockModuleDef.java b/dev/core/test/com/google/gwt/dev/cfg/MockModuleDef.java
index 7f1395c..fe89967 100644
--- a/dev/core/test/com/google/gwt/dev/cfg/MockModuleDef.java
+++ b/dev/core/test/com/google/gwt/dev/cfg/MockModuleDef.java
@@ -56,9 +56,16 @@
     }
   };
 
+  private Properties properties = new Properties();
+
   public MockModuleDef() {
     super("mock");
     normalize(TreeLogger.NULL);
     lazyPublicOracle = new MockResourceOracle(publicResource);
   }
+
+  @Override
+  public Properties getProperties() {
+    return properties;
+  }
 }
diff --git a/dev/core/test/com/google/gwt/dev/javac/CheckerTestCase.java b/dev/core/test/com/google/gwt/dev/javac/CheckerTestCase.java
index a1ef78f..8967543 100644
--- a/dev/core/test/com/google/gwt/dev/javac/CheckerTestCase.java
+++ b/dev/core/test/com/google/gwt/dev/javac/CheckerTestCase.java
@@ -23,10 +23,10 @@
 import com.google.gwt.dev.resource.Resource;
 import com.google.gwt.dev.util.UnitTestTreeLogger;
 import com.google.gwt.thirdparty.guava.common.base.Joiner;
+import com.google.gwt.thirdparty.guava.common.collect.Sets;
 
 import junit.framework.TestCase;
 
-import java.util.HashSet;
 import java.util.Set;
 
 /**
@@ -35,6 +35,18 @@
 public abstract class CheckerTestCase extends TestCase {
 
   /**
+   * Pass represents passes to be run by the checker test cases.
+   */
+  public interface Pass {
+    /**
+     * Run the pass. Returns true if pass completes successfully.
+     */
+    boolean run(TreeLogger logger, MockJavaResource buggyResource, MockJavaResource extraResource);
+
+    boolean classAvailable(String className);
+    String getTopErrorMessage(Type logLevel, MockJavaResource resource);
+  }
+  /**
    * A warning or error message.
    */
   protected static class Message {
@@ -49,6 +61,41 @@
     }
   }
 
+  /**
+   * Provide a pass to be checked by default runs only passes required to build TypeOracle.
+   */
+  protected Pass providePass() {
+    return new Pass() {
+      private TypeOracle oracle = null;
+      @Override
+      public boolean run(TreeLogger logger, MockJavaResource buggyResource,
+          MockJavaResource extraResource) {
+        Set<Resource> resources = Sets.newHashSet();
+        addLongCheckingCups(resources);
+        Set<GeneratedUnit> generatedUnits =
+            CompilationStateTestBase.getGeneratedUnits(buggyResource);
+        if (extraResource != null) {
+          generatedUnits.addAll(CompilationStateTestBase.getGeneratedUnits(extraResource));
+        }
+        CompilationState state  = TypeOracleTestingUtils.buildCompilationStateWith(logger,
+            resources, generatedUnits);
+        oracle = state.getTypeOracle();
+        return !state.hasErrors();
+      }
+
+      @Override
+      public boolean classAvailable(String className) {
+        return oracle.findType(className) != null;
+      }
+
+      @Override
+      public String getTopErrorMessage(Type logLevel, MockJavaResource resource) {
+        return (logLevel == Type.WARN ? "Warnings" : "Errors") +
+            " in '" + resource.getLocation() + "'";
+      }
+    };
+  }
+
   protected Message warning(int line, String message) {
     return new Message(line, Type.WARN, message);
   }
@@ -61,26 +108,32 @@
       Type logLevel, Message... messages) {
     UnitTestTreeLogger.Builder b = new UnitTestTreeLogger.Builder();
     b.setLowestLogLevel(logLevel);
-    if (messages.length != 0) {
-      b.expect(logLevel, (logLevel == Type.WARN ? "Warnings" : "Errors") +
-          " in '" + buggyCode.getLocation() + "'", null);
+    Pass pass = providePass();
+    String topLevelMessage = pass.getTopErrorMessage(logLevel, buggyCode);
+    if (messages.length != 0 && topLevelMessage != null) {
+      b.expect(logLevel, topLevelMessage, null);
     }
     for (Message message : messages) {
       final String fullMessage = "Line " + message.line + ": " + message.message;
       b.expect(message.logLevel, fullMessage, null);
     }
     UnitTestTreeLogger logger = b.createLogger();
-    TypeOracle oracle = buildOracle(logger, buggyCode, extraCode);
+
+    boolean result = pass.run(logger, buggyCode, extraCode);
 
     logger.assertCorrectLogEntries();
     String className = buggyCode.getTypeName();
     if (messages.length != 0 && logLevel == TreeLogger.ERROR) {
-      assertNull("Buggy compilation unit not removed from type oracle",
-          oracle.findType(className));
+      assertFalse("Compilation unit " + className + " not removed" +
+              " but should have been removed.",
+          pass.classAvailable(className));
     } else {
-      assertNotNull("Buggy compilation unit removed with only a warning",
-          oracle.findType(className));
+      assertTrue("Compilation unit " + className + " was removed but shouldnt have.",
+          pass.classAvailable(className));
     }
+
+    boolean expectingErrors = messages.length != 0 && logLevel == Type.ERROR;
+    assertEquals(!expectingErrors, result);
   }
 
   protected void shouldGenerateError(MockJavaResource buggyCode, MockJavaResource extraCode,
@@ -88,13 +141,6 @@
     shouldGenerate(buggyCode, extraCode, TreeLogger.ERROR, error(line, message));
   }
 
-  protected void shouldGenerateError(CharSequence buggyCode, CharSequence extraCode, int line,
-      String message) {
-    StaticJavaResource codeResource = new StaticJavaResource("Buggy", buggyCode);
-    StaticJavaResource extraResource = new StaticJavaResource("Extra", extraCode);
-    shouldGenerate(codeResource, extraResource, TreeLogger.ERROR,  error(line, message));
-  }
-
   protected void shouldGenerateError(MockJavaResource buggyCode, int line,  String message) {
     shouldGenerateError(buggyCode, null, line, message);
   }
@@ -107,12 +153,6 @@
     shouldGenerate(code, extraCode, TreeLogger.ERROR);
   }
 
-  protected void shouldGenerateNoError(CharSequence code, CharSequence extraCode) {
-    StaticJavaResource codeResource = new StaticJavaResource("Buggy", code);
-    StaticJavaResource extraResource = new StaticJavaResource("Extra", extraCode);
-    shouldGenerate(codeResource, extraResource, TreeLogger.ERROR);
-  }
-
   protected void shouldGenerateNoWarning(MockJavaResource code) {
     shouldGenerateNoWarning(code, null);
   }
@@ -139,7 +179,6 @@
       Message... messages) {
     shouldGenerate(buggyCode, extraCode, TreeLogger.WARN, messages);
   }
-  private String buggyPackage = "";
 
   private void addLongCheckingCups(Set<Resource> resources) {
     String code = Joiner.on('\n').join(
@@ -149,17 +188,4 @@
     resources.add(new StaticJavaResource(
         "com.google.gwt.core.client.UnsafeNativeLong", code.toString()));
   }
-
-  private TypeOracle buildOracle(UnitTestTreeLogger logger, MockJavaResource buggyResource,
-      MockJavaResource extraResource) {
-    Set<Resource> resources = new HashSet<Resource>();
-    addLongCheckingCups(resources);
-    Set<GeneratedUnit> generatedUnits = CompilationStateTestBase.getGeneratedUnits(buggyResource);
-    if (extraResource != null) {
-      generatedUnits.addAll(CompilationStateTestBase.getGeneratedUnits(extraResource));
-    }
-    return TypeOracleTestingUtils.buildStandardTypeOracleWith(logger,
-        resources, generatedUnits);
-  }
-
 }
diff --git a/dev/core/test/com/google/gwt/dev/javac/TypeOracleTestingUtils.java b/dev/core/test/com/google/gwt/dev/javac/TypeOracleTestingUtils.java
index ed642e7..a92e929 100644
--- a/dev/core/test/com/google/gwt/dev/javac/TypeOracleTestingUtils.java
+++ b/dev/core/test/com/google/gwt/dev/javac/TypeOracleTestingUtils.java
@@ -22,6 +22,7 @@
 import com.google.gwt.dev.javac.testing.impl.JavaResourceBase;
 import com.google.gwt.dev.resource.Resource;
 import com.google.gwt.dev.util.arg.SourceLevel;
+import com.google.gwt.thirdparty.guava.common.collect.Sets;
 
 import java.util.Arrays;
 import java.util.Collections;
@@ -72,36 +73,13 @@
     return buildUpdaterWith(logger, standardBuildersPlus(resources));
   }
 
-  public static TypeOracle buildStandardTypeOracleWith(TreeLogger logger,
-      Resource... resources) {
-    return buildStandardTypeOracleWith(logger, new HashSet<Resource>(
-        Arrays.asList(resources)));
-  }
-
-  public static TypeOracle buildStandardTypeOracleWith(TreeLogger logger,
-      Set<Resource> resources) {
-    return buildTypeOracle(logger, standardBuildersPlus(resources));
-  }
-
-  public static TypeOracle buildStandardTypeOracleWith(TreeLogger logger,
-      Set<Resource> resources, Set<GeneratedUnit> generatedUnits, SourceLevel sourceLevel) {
-    return buildTypeOracle(logger, standardBuildersPlus(resources),
-        generatedUnits, sourceLevel);
-  }
-
-  public static TypeOracle buildStandardTypeOracleWith(TreeLogger logger,
+  public static CompilationState buildCompilationStateWith(TreeLogger logger,
       Set<Resource> resources, Set<GeneratedUnit> generatedUnits) {
-    return buildTypeOracle(logger, standardBuildersPlus(resources),
+    return buildCompilationState(logger, standardBuildersPlus(resources),
         generatedUnits, SourceLevel.DEFAULT_SOURCE_LEVEL);
   }
 
-  public static TypeOracle buildTypeOracle(TreeLogger logger,
-      Set<Resource> resources) {
-    return buildTypeOracle(logger, resources,
-        Collections.<GeneratedUnit> emptySet());
-  }
-
-  public static TypeOracle buildTypeOracle(TreeLogger logger,
+  public static CompilationState buildCompilationState(TreeLogger logger,
       Set<Resource> resources, Set<GeneratedUnit> generatedUnits, SourceLevel sourceLevel) {
     try {
       CompilerContext compilerContext = new CompilerContext();
@@ -110,15 +88,38 @@
       CompilationState state =
           CompilationStateBuilder.buildFrom(logger, compilerContext, resources);
       state.addGeneratedCompilationUnits(logger, generatedUnits);
-      return state.getTypeOracle();
+      return state;
     } catch (UnableToCompleteException e) {
       throw new RuntimeException(e);
     }
   }
 
+  public static TypeOracle buildTypeOracle(TreeLogger logger, Set<Resource> resources) {
+    return buildTypeOracle(logger, resources,
+        Collections.<GeneratedUnit>emptySet());
+  }
+
   public static TypeOracle buildTypeOracle(TreeLogger logger,
       Set<Resource> resources, Set<GeneratedUnit> generatedUnits) {
-    return buildTypeOracle(logger, resources, generatedUnits, SourceLevel.DEFAULT_SOURCE_LEVEL);
+    return buildCompilationState(logger, resources, generatedUnits,
+        SourceLevel.DEFAULT_SOURCE_LEVEL).getTypeOracle();
+  }
+
+  public static TypeOracle buildStandardTypeOracleWith(TreeLogger logger,
+      Set<Resource> resources, Set<GeneratedUnit> generatedUnits, SourceLevel sourceLevel) {
+    return buildCompilationState(logger, standardBuildersPlus(resources),
+        generatedUnits, sourceLevel).getTypeOracle();
+  }
+
+  public static TypeOracle buildStandardTypeOracleWith(TreeLogger logger,
+      Set<Resource> resources) {
+    return buildTypeOracle(logger, standardBuildersPlus(resources));
+  }
+
+  public static TypeOracle buildStandardTypeOracleWith(TreeLogger logger,
+      Resource... resources) {
+    return buildStandardTypeOracleWith(logger, Sets.newHashSet(
+        Arrays.asList(resources)));
   }
 
   /**
diff --git a/dev/core/test/com/google/gwt/dev/jjs/JavaAstConstructor.java b/dev/core/test/com/google/gwt/dev/jjs/JavaAstConstructor.java
index cdd6c99..0023311 100644
--- a/dev/core/test/com/google/gwt/dev/jjs/JavaAstConstructor.java
+++ b/dev/core/test/com/google/gwt/dev/jjs/JavaAstConstructor.java
@@ -17,7 +17,7 @@
 
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
-import com.google.gwt.dev.PrecompileTaskOptions;
+import com.google.gwt.dev.CompilerContext;
 import com.google.gwt.dev.cfg.ConfigurationProperties;
 import com.google.gwt.dev.javac.CompilationState;
 import com.google.gwt.dev.javac.testing.impl.JavaResourceBase;
@@ -353,10 +353,10 @@
   };
 
   public static JProgram construct(TreeLogger logger, CompilationState state,
-      PrecompileTaskOptions options, ConfigurationProperties config,
+      CompilerContext compilerContext, ConfigurationProperties config,
       String... entryPoints) throws UnableToCompleteException {
-    options.setEnableAssertions(true);
-    JProgram jprogram = AstConstructor.construct(logger, state, options, config);
+    compilerContext.getOptions().setEnableAssertions(true);
+    JProgram jprogram = AstConstructor.construct(logger, state, compilerContext, config);
 
     // Add entry methods for entry points.
     for (String entryPoint : entryPoints) {
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/ComputeExhaustiveCastabilityInformationTest.java b/dev/core/test/com/google/gwt/dev/jjs/impl/ComputeExhaustiveCastabilityInformationTest.java
index 7bb5d26..92ad1e4 100644
--- a/dev/core/test/com/google/gwt/dev/jjs/impl/ComputeExhaustiveCastabilityInformationTest.java
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/ComputeExhaustiveCastabilityInformationTest.java
@@ -40,7 +40,7 @@
     registerCompilableResources();
 
     // Compiles and gets a reference to the String[] type.
-    JProgram program = compileSnippet("void", "", "", false, true);
+    JProgram program = compileSnippet(null, "void", "", "", true);
     ComputeExhaustiveCastabilityInformation.exec(program);
     JDeclaredType stringType = program.getIndexedType("String");
     JArrayType stringArrayType = program.getTypeArray(stringType);
@@ -84,7 +84,7 @@
 
   private void assertSourceCastsToTargets(String sourceTypeName,
       Set<String> expectedTargetTypeNames) throws UnableToCompleteException {
-    JProgram program = compileSnippet("void", "", "", false, true);
+    JProgram program = compileSnippet(null, "void", "", "", true);
     ComputeExhaustiveCastabilityInformation.exec(program);
     JDeclaredType sourceType = program.getIndexedType(sourceTypeName);
     assertSourceCastsToTargets(program, sourceType, expectedTargetTypeNames);
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/FullCompileTestBase.java b/dev/core/test/com/google/gwt/dev/jjs/impl/FullCompileTestBase.java
index 11ac8cc..35af944 100644
--- a/dev/core/test/com/google/gwt/dev/jjs/impl/FullCompileTestBase.java
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/FullCompileTestBase.java
@@ -82,7 +82,7 @@
         configurationProperties));
 
     jProgram =
-        JavaAstConstructor.construct(logger, state, compilerContext.getOptions(), config,
+        JavaAstConstructor.construct(logger, state, compilerContext, config,
             "test.EntryPoint", "com.google.gwt.lang.Exceptions");
     jProgram.addEntryMethod(findMethod(jProgram, "onModuleLoad"));
 
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/GwtAstBuilderTest.java b/dev/core/test/com/google/gwt/dev/jjs/impl/GwtAstBuilderTest.java
index 6460ffa..f93ca29 100644
--- a/dev/core/test/com/google/gwt/dev/jjs/impl/GwtAstBuilderTest.java
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/GwtAstBuilderTest.java
@@ -262,18 +262,11 @@
   }
 
   private JProgram compileProgram(String entryType) throws UnableToCompleteException {
-    CompilerContext compilerContext =
-        new CompilerContext.Builder().options(new PrecompileTaskOptionsImpl() {
-            @Override
-          public boolean shouldJDTInlineCompileTimeConstants() {
-            return false;
-          }
-        }).build();
-    compilerContext.getOptions().setSourceLevel(sourceLevel);
-    compilerContext.getOptions().setStrict(true);
+    CompilerContext compilerContext = provideCompilerContext();
+;
     CompilationState state = CompilationStateBuilder.buildFrom(logger, compilerContext, sources,
         getAdditionalTypeProviderDelegate());
-    JProgram program = JavaAstConstructor.construct(logger, state, compilerContext.getOptions(),
+    JProgram program = JavaAstConstructor.construct(logger, state, compilerContext,
         null, entryType, "com.google.gwt.lang.Exceptions");
     return program;
   }
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/JJSTestBase.java b/dev/core/test/com/google/gwt/dev/jjs/impl/JJSTestBase.java
index a36ee47..7e53200 100644
--- a/dev/core/test/com/google/gwt/dev/jjs/impl/JJSTestBase.java
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/JJSTestBase.java
@@ -19,6 +19,8 @@
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.dev.CompilerContext;
 import com.google.gwt.dev.PrecompileTaskOptionsImpl;
+import com.google.gwt.dev.cfg.MockModuleDef;
+import com.google.gwt.dev.javac.CheckerTestCase;
 import com.google.gwt.dev.javac.CompilationState;
 import com.google.gwt.dev.javac.CompilationStateBuilder;
 import com.google.gwt.dev.javac.JdtCompiler.AdditionalTypeProviderDelegate;
@@ -51,8 +53,6 @@
 import com.google.gwt.thirdparty.guava.common.collect.Lists;
 import com.google.gwt.thirdparty.guava.common.collect.Sets;
 
-import junit.framework.TestCase;
-
 import java.util.Arrays;
 import java.util.List;
 import java.util.Set;
@@ -62,7 +62,7 @@
 /**
  * A useful base class for tests that build JJS ASTs.
  */
-public abstract class JJSTestBase extends TestCase {
+public abstract class JJSTestBase extends CheckerTestCase {
 
   public static final String MAIN_METHOD_NAME = "onModuleLoad";
 
@@ -245,42 +245,68 @@
   /**
    * Returns the program that results from compiling the specified code snippet
    * as the body of an entry point method.
-   *  @param returnType the return type of the method to compile; use "void" if
+   * @param logger a logger where to log, the default logger will be used if null.
+   * @param returnType the return type of the method to compile; use "void" if
    *          the code snippet has no return statement
    * @param codeSnippet the body of the entry method
    */
-  protected JProgram compileSnippet(final String returnType,
+  protected JProgram compileSnippet(TreeLogger logger,  final String returnType,
       final String codeSnippet) throws UnableToCompleteException {
-    return compileSnippet(returnType, "", codeSnippet, true, false);
+    return compileSnippet(logger, returnType, "", codeSnippet, false);
   }
 
   /**
    * Returns the program that results from compiling the specified code snippet
    * as the body of an entry point method.
-   *  @param returnType the return type of the method to compile; use "void" if
+   * @param logger a logger where to log, the default logger will be used if null.
+   * @param returnType the return type of the method to compile; use "void" if
+   *          the code snippet has no return statement
+   * @param codeSnippet the body of the entry method
+   * @param staticMethod whether to make the method static
+   */
+  protected JProgram compileSnippet(TreeLogger logger,  final String returnType,
+      final String codeSnippet, boolean staticMethod) throws UnableToCompleteException {
+    return compileSnippet(logger, returnType, "", codeSnippet, staticMethod);
+  }
+
+  /**
+   * Returns the program that results from compiling the specified code snippet
+   * as the body of an entry point method.
+   * @param returnType the return type of the method to compile; use "void" if
    *          the code snippet has no return statement
    * @param codeSnippet the body of the entry method
    * @param staticMethod whether to make the method static
    */
   protected JProgram compileSnippet(final String returnType,
       final String codeSnippet, boolean staticMethod) throws UnableToCompleteException {
-    return compileSnippet(returnType, "", codeSnippet, true, staticMethod);
+    return compileSnippet(logger, returnType, "", codeSnippet, staticMethod);
+  }
+
+  /**
+   * Returns the program that results from compiling the specified code snippet
+   * as the body of an entry point method.
+   * @param returnType the return type of the method to compile; use "void" if
+   *          the code snippet has no return statement
+   * @param codeSnippet the body of the entry method
+   */
+  protected JProgram compileSnippet(final String returnType,
+      final String codeSnippet) throws UnableToCompleteException {
+    return compileSnippet(logger, returnType, "", codeSnippet, false);
   }
 
   /**
    * Returns the program that results from compiling the specified code snippet
    * as the body of an entry point method.
    *
+   * @param logger a logger where to log, the default logger will be used if null.
    * @param returnType the return type of the method to compile; use "void" if
    *          the code snippet has no return statement
    * @param params the parameter list of the method to compile
    * @param codeSnippet the body of the entry method
-   * @param compileMonolithic whether the compile is monolithic
    * @param staticMethod whether the entryPoint should be static
    */
-  protected JProgram compileSnippet(final String returnType,
-      final String params, final String codeSnippet, boolean compileMonolithic,
-      final boolean staticMethod)
+  protected JProgram compileSnippet(TreeLogger logger, final String returnType,
+      final String params, final String codeSnippet, final boolean staticMethod)
       throws UnableToCompleteException {
     sourceOracle.addOrReplace(new MockJavaResource("test.EntryPoint") {
       @Override
@@ -302,26 +328,40 @@
         return code;
       }
     });
-    CompilerContext compilerContext =
-        new CompilerContext.Builder().options(new PrecompileTaskOptionsImpl() {
-          @Override
-          public boolean shouldJDTInlineCompileTimeConstants() {
-            return false;
-          }
-        }).build();
-    compilerContext.getOptions().setSourceLevel(sourceLevel);
-    compilerContext.getOptions().setStrict(true);
-    compilerContext.getOptions().setJsInteropMode(OptionJsInteropMode.Mode.JS);
+    CompilerContext compilerContext = provideCompilerContext();
+
+    if (logger == null)  {
+      logger = this.logger;
+    }
     CompilationState state =
         CompilationStateBuilder.buildFrom(logger, compilerContext,
             sourceOracle.getResources(), getAdditionalTypeProviderDelegate());
     JProgram program =
-        JavaAstConstructor.construct(logger, state, compilerContext.getOptions(),
+        JavaAstConstructor.construct(logger, state, compilerContext,
             null, "test.EntryPoint", "com.google.gwt.lang.Exceptions");
     return program;
   }
 
   /**
+   * Returns a compiler context to be used for compiling code within the test.
+   */
+  protected CompilerContext provideCompilerContext() {
+    CompilerContext compilerContext = new CompilerContext.Builder().module(new MockModuleDef())
+        .options(new PrecompileTaskOptionsImpl() {
+                   @Override
+                   public boolean shouldJDTInlineCompileTimeConstants() {
+                     return false;
+                   }
+                 }
+        ).build();
+
+    compilerContext.getOptions().setSourceLevel(sourceLevel);
+    compilerContext.getOptions().setStrict(true);
+    compilerContext.getOptions().setJsInteropMode(OptionJsInteropMode.Mode.JS);
+    return compilerContext;
+  }
+
+  /**
    * Return an AdditionalTypeProviderDelegate that will be able to provide
    * new sources for unknown classnames.
    */
@@ -420,7 +460,9 @@
 
   protected void addAll(Resource... sourceFiles) {
     for (Resource sourceFile : sourceFiles) {
-      sourceOracle.addOrReplace(sourceFile);
+      if (sourceFile != null) {
+        sourceOracle.addOrReplace(sourceFile);
+      }
     }
   }
 
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/OptimizerTestBase.java b/dev/core/test/com/google/gwt/dev/jjs/impl/OptimizerTestBase.java
index 2242d49..660210d 100644
--- a/dev/core/test/com/google/gwt/dev/jjs/impl/OptimizerTestBase.java
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/OptimizerTestBase.java
@@ -476,14 +476,17 @@
   protected final Result optimizeMethod(final String methodName,
       final String mainMethodReturnType, final String... mainMethodSnippet)
       throws UnableToCompleteException {
-    return optimizeMethod(TreeLogger.NULL, methodName, mainMethodReturnType, mainMethodSnippet);
+    return optimizeMethod(null, methodName, mainMethodReturnType, mainMethodSnippet);
   }
 
   protected final Result optimizeMethod(TreeLogger logger, final String methodName,
       final String mainMethodReturnType, final String... mainMethodSnippet)
       throws UnableToCompleteException {
+    if (logger == null) {
+      logger = this.logger;
+    }
     String snippet = Joiner.on("\n").join(mainMethodSnippet);
-    JProgram program = compileSnippet(mainMethodReturnType, snippet, true);
+    JProgram program = compileSnippet(logger, mainMethodReturnType, snippet, true);
     JMethod method = findMethod(program, methodName);
     boolean madeChanges = doOptimizeMethod(logger, program, method);
     if (madeChanges && runDeadCodeElimination) {
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/UnifyAstTest.java b/dev/core/test/com/google/gwt/dev/jjs/impl/UnifyAstTest.java
index a2161c9..9042d39 100644
--- a/dev/core/test/com/google/gwt/dev/jjs/impl/UnifyAstTest.java
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/UnifyAstTest.java
@@ -14,7 +14,9 @@
 package com.google.gwt.dev.jjs.impl;
 
 import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.TreeLogger.Type;
 import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.dev.CompilerContext;
 import com.google.gwt.dev.javac.testing.impl.JavaResourceBase;
 import com.google.gwt.dev.javac.testing.impl.MockJavaResource;
 import com.google.gwt.dev.jjs.ast.JClassType;
@@ -480,12 +482,102 @@
             .typeOracle.getInstanceMethodBySignature(testImplClass, mSignature));
   }
 
+  public void testGetProperty_error_undefinedProperty()
+      throws UnableToCompleteException {
+
+    final MockJavaResource someClass =
+        JavaResourceBase.createMockJavaResource("test.SomeClass",
+            "package test;",
+            "public class SomeClass {",
+            "  public void m() { String a = System.getProperty(\"undefined\"); }",
+            "}");
+
+    shouldGenerateError(someClass, 3, "Property 'undefined' is not defined.");
+  }
+
+  public void testGetProperty_error_multivalued()
+      throws UnableToCompleteException {
+
+    final MockJavaResource someClass =
+        JavaResourceBase.createMockJavaResource("test.SomeClass",
+            "package test;",
+            "public class SomeClass {",
+            "  public void m() { String a = System.getProperty(\"multivalued\"); }",
+            "}");
+
+    shouldGenerateError(someClass, 3, "Property 'multivalued' is multivalued." +
+        " Multivalued properties are not supported by System.getProperty().");
+  }
+
+  public void testGetPropertyWithDefault_error_multivalued()
+      throws UnableToCompleteException {
+
+    final MockJavaResource someClass =
+        JavaResourceBase.createMockJavaResource("test.SomeClass",
+            "package test;",
+            "public class SomeClass {",
+            "  public void m() { String a = System.getProperty(\"multivalued\", \"somevalue\"); }",
+            "}");
+
+    shouldGenerateError(someClass, 3, "Property 'multivalued' is multivalued." +
+        " Multivalued properties are not supported by System.getProperty().");
+  }
+
+  public void testGetPropertyWithDefault_success_undefined()
+      throws UnableToCompleteException {
+
+    final MockJavaResource someClass =
+        JavaResourceBase.createMockJavaResource("test.SomeClass",
+            "package test;",
+            "public class SomeClass {",
+            "  public void m() { String a = System.getProperty(\"undefined\", \"somevalue\"); }",
+            "}");
+
+    shouldGenerateNoError(someClass);
+  }
+
   @Override
   protected boolean doOptimizeMethod(TreeLogger logger, JProgram program, JMethod method) {
     program.addEntryMethod(findMainMethod(program));
     return false;
   }
 
+  @Override
+  protected CompilerContext provideCompilerContext() {
+    CompilerContext context = super.provideCompilerContext();
+    context.getModule().getProperties().createConfiguration("multivalued", true);
+    return context;
+  }
+
+  @Override
+  protected Pass providePass() {
+    return new Pass() {
+      private Result result = null;
+      @Override
+      public boolean run(TreeLogger logger, MockJavaResource buggyResource,
+          MockJavaResource extraResource) {
+        addAll(buggyResource, extraResource);
+        try {
+          result = optimize(logger, "void", "new SomeClass().m();");
+        } catch (UnableToCompleteException e) {
+          return false;
+        }
+        return true;
+      }
+
+      @Override
+      public boolean classAvailable(String className) {
+        return result != null && result.findClass(className) != null;
+      }
+
+      @Override
+      public String getTopErrorMessage(Type logLevel, MockJavaResource resource) {
+        return (logLevel == Type.WARN ? "Warnings" : "Errors") +
+            " in '" + resource.getPath() + "'";
+      }
+    };
+  }
+
   private static final MockJavaResource A_A =
       JavaResourceBase.createMockJavaResource("a.A",
           "package a;",
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/gflow/CfgAnalysisTestBase.java b/dev/core/test/com/google/gwt/dev/jjs/impl/gflow/CfgAnalysisTestBase.java
index 8ba8fd2..9d17cf6 100644
--- a/dev/core/test/com/google/gwt/dev/jjs/impl/gflow/CfgAnalysisTestBase.java
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/gflow/CfgAnalysisTestBase.java
@@ -42,7 +42,7 @@
 
   protected AnalysisResult analyzeWithParams(String returnType, String params,
       String... codeSnippet) throws UnableToCompleteException {
-    JProgram program = compileSnippet(returnType, params, Joiner.on("\n").join(codeSnippet), true,
+    JProgram program = compileSnippet(null, returnType, params, Joiner.on("\n").join(codeSnippet),
         true);
     JMethodBody body = (JMethodBody) findMainMethod(program).getBody();
     Cfg cfgGraph = CfgBuilder.build(program, body.getBlock());
diff --git a/user/test/com/google/gwt/dev/jjs/test/SystemGetPropertyTest.java b/user/test/com/google/gwt/dev/jjs/test/SystemGetPropertyTest.java
index 4d96a90..2604e5e 100644
--- a/user/test/com/google/gwt/dev/jjs/test/SystemGetPropertyTest.java
+++ b/user/test/com/google/gwt/dev/jjs/test/SystemGetPropertyTest.java
@@ -27,7 +27,7 @@
     return "com.google.gwt.dev.jjs.SystemGetPropertyTest";
   }
 
-  // TODO(rluble): Remove DoNotRun here is System.getProperty is ever implemented for devmode.
+  // In DevMode System.getProperty is routed to the standard Java implementation.
   @DoNotRunWith(Platform.Devel)
   public void testBindingProperties() {
     assertEquals("two", System.getProperty("collapsedProperty"));
@@ -40,7 +40,6 @@
 
   @DoNotRunWith(Platform.Devel)
   public void testConfigurationProperties() {
-    assertNull(System.getProperty("nonExistent"));
     assertEquals("conf", System.getProperty("someConfigurationProperty"));
     assertEquals("conf", System.getProperty("someConfigurationProperty", "default"));
   }