[vm] Add source positions to stack traces of bool checks

If a condition has a source position in kernel, this source position is
now used in stack traces for bool checks.

Issue: https://github.com/dart-lang/sdk/issues/33584
Change-Id: Idb833abbaeaa32e6c5deb0d383cc4215642d3f2a
Reviewed-on: https://dart-review.googlesource.com/62360
Reviewed-by: Samir Jindel <sjindel@google.com>
Commit-Queue: Alexander Markov <alexmarkov@google.com>
diff --git a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc
index 23b2002..86b3e06 100644
--- a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc
+++ b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.cc
@@ -8315,8 +8315,8 @@
   return flow_graph_builder_->BuildImplicitClosureCreation(target);
 }
 
-Fragment StreamingFlowGraphBuilder::CheckBoolean() {
-  return flow_graph_builder_->CheckBoolean();
+Fragment StreamingFlowGraphBuilder::CheckBoolean(TokenPosition position) {
+  return flow_graph_builder_->CheckBoolean(position);
 }
 
 Fragment StreamingFlowGraphBuilder::CheckAssignableInCheckedMode(
@@ -8375,8 +8375,9 @@
   if (*negate) {
     SkipBytes(1);  // Skip Not tag, thus go directly to the inner expression.
   }
-  Fragment instructions = BuildExpression();  // read expression.
-  instructions += CheckBoolean();
+  TokenPosition position = TokenPosition::kNoSource;
+  Fragment instructions = BuildExpression(&position);  // read expression.
+  instructions += CheckBoolean(position);
   return instructions;
 }
 
@@ -9621,8 +9622,10 @@
 Fragment StreamingFlowGraphBuilder::BuildNot(TokenPosition* position) {
   if (position != NULL) *position = TokenPosition::kNoSource;
 
-  Fragment instructions = BuildExpression();  // read expression.
-  instructions += CheckBoolean();
+  TokenPosition operand_position = TokenPosition::kNoSource;
+  Fragment instructions =
+      BuildExpression(&operand_position);  // read expression.
+  instructions += CheckBoolean(operand_position);
   instructions += BooleanNegate();
   return instructions;
 }
@@ -10291,17 +10294,18 @@
   // The call to `_AssertionError._evaluateAssertion()` will take care of both
   // and returns a boolean.
   instructions += BuildExpression();  // read condition.
+
+  const TokenPosition condition_start_offset =
+      ReadPosition();  // read condition start offset.
+  const TokenPosition condition_end_offset =
+      ReadPosition();  // read condition end offset.
+
   instructions += PushArgument();
   instructions += EvaluateAssertion();
-  instructions += CheckBoolean();
+  instructions += CheckBoolean(condition_start_offset);
   instructions += Constant(Bool::True());
   instructions += BranchIfEqual(&then, &otherwise, false);
 
-  TokenPosition condition_start_offset =
-      ReadPosition();  // read condition start offset.
-  TokenPosition condition_end_offset =
-      ReadPosition();  // read condition end offset.
-
   const Class& klass =
       Class::ZoneHandle(Z, Library::LookupCoreClass(Symbols::AssertionError()));
   ASSERT(!klass.IsNull());
diff --git a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.h b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.h
index 5b1c7dd..2636f59 100644
--- a/runtime/vm/compiler/frontend/kernel_binary_flowgraph.h
+++ b/runtime/vm/compiler/frontend/kernel_binary_flowgraph.h
@@ -1492,7 +1492,7 @@
   JoinEntryInstr* BuildJoinEntry(intptr_t try_index);
   Fragment Goto(JoinEntryInstr* destination);
   Fragment BuildImplicitClosureCreation(const Function& target);
-  Fragment CheckBoolean();
+  Fragment CheckBoolean(TokenPosition position);
   Fragment CheckAssignableInCheckedMode(const AbstractType& dst_type,
                                         const String& dst_name);
   Fragment CheckArgumentType(LocalVariable* variable, const AbstractType& type);
diff --git a/runtime/vm/compiler/frontend/kernel_to_il.cc b/runtime/vm/compiler/frontend/kernel_to_il.cc
index 1bb584e..7d677b5 100644
--- a/runtime/vm/compiler/frontend/kernel_to_il.cc
+++ b/runtime/vm/compiler/frontend/kernel_to_il.cc
@@ -2422,12 +2422,12 @@
                     ICData::kStatic);
 }
 
-Fragment FlowGraphBuilder::CheckBoolean() {
+Fragment FlowGraphBuilder::CheckBoolean(TokenPosition position) {
   Fragment instructions;
   if (I->strong() || I->type_checks() || I->asserts()) {
     LocalVariable* top_of_stack = MakeTemporary();
     instructions += LoadLocal(top_of_stack);
-    instructions += AssertBool();
+    instructions += AssertBool(position);
     instructions += Drop();
   }
   return instructions;
@@ -2451,10 +2451,10 @@
   return instructions;
 }
 
-Fragment FlowGraphBuilder::AssertBool() {
+Fragment FlowGraphBuilder::AssertBool(TokenPosition position) {
   Value* value = Pop();
-  AssertBooleanInstr* instr = new (Z)
-      AssertBooleanInstr(TokenPosition::kNoSource, value, GetNextDeoptId());
+  AssertBooleanInstr* instr =
+      new (Z) AssertBooleanInstr(position, value, GetNextDeoptId());
   Push(instr);
   return Fragment(instr);
 }
diff --git a/runtime/vm/compiler/frontend/kernel_to_il.h b/runtime/vm/compiler/frontend/kernel_to_il.h
index 9bb30e5..3ef59b2 100644
--- a/runtime/vm/compiler/frontend/kernel_to_il.h
+++ b/runtime/vm/compiler/frontend/kernel_to_il.h
@@ -824,13 +824,13 @@
   Fragment EvaluateAssertion();
   Fragment CheckVariableTypeInCheckedMode(const AbstractType& dst_type,
                                           const String& name_symbol);
-  Fragment CheckBoolean();
+  Fragment CheckBoolean(TokenPosition position);
   Fragment CheckAssignable(
       const AbstractType& dst_type,
       const String& dst_name,
       AssertAssignableInstr::Kind kind = AssertAssignableInstr::kUnknown);
 
-  Fragment AssertBool();
+  Fragment AssertBool(TokenPosition position);
   Fragment AssertAssignable(
       TokenPosition position,
       const AbstractType& dst_type,
diff --git a/tests/language_2/language_2_kernel.status b/tests/language_2/language_2_kernel.status
index c456c91..b54747c 100644
--- a/tests/language_2/language_2_kernel.status
+++ b/tests/language_2/language_2_kernel.status
@@ -832,6 +832,7 @@
 type_promotion_functions_test/none: CompileTimeError # Issue 31537
 type_promotion_logical_and_test/01: MissingCompileTimeError
 type_promotion_more_specific_test/04: CompileTimeError # Issue 31533
+vm/bool_check_stack_traces_test/02: RuntimeError # Issue 33584
 vm/closure_memory_retention_test: Skip # KernelVM bug: Hits OOM
 vm/regress_29145_test: Skip # Issue 29145
 vm/type_cast_vm_test: RuntimeError
@@ -900,6 +901,8 @@
 type_alias_equality_test/02: RuntimeError # Issue 31359
 type_alias_equality_test/03: RuntimeError # Issue 31359
 type_alias_equality_test/04: RuntimeError # Issue 31359
+vm/bool_check_stack_traces_test/01: RuntimeError # Issue 33584
+vm/bool_check_stack_traces_test/02: RuntimeError # Issue 33584
 
 [ $compiler == dartkp && $runtime == dart_precompiled && $checked && $strong ]
 assert_initializer_test/31: MissingCompileTimeError # KernelVM bug: Constant evaluation.
diff --git a/tests/language_2/vm/bool_check_stack_traces_test.dart b/tests/language_2/vm/bool_check_stack_traces_test.dart
new file mode 100644
index 0000000..cd80936
--- /dev/null
+++ b/tests/language_2/vm/bool_check_stack_traces_test.dart
@@ -0,0 +1,103 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import "package:expect/expect.dart";
+
+class Test1 {
+  void bar(bool condition) {
+    if (condition) {
+      Expect.fail('Should not reach here');
+    }
+    Expect.fail('Should throw earlier');
+  }
+}
+
+void test1(bool condition) {
+  Test1 obj = new Test1();
+  obj.bar(condition);
+}
+
+class Test2 {
+  bool condition;
+  Test2(this.condition);
+
+  void bar() {
+    if (!condition) {
+      Expect.fail('Should not reach here');
+    }
+    Expect.fail('Should throw earlier');
+  }
+}
+
+void test2(bool condition) {
+  Test2 obj = new Test2(condition);
+  obj.bar();
+}
+
+class Test3 {
+  bool condition;
+  Test3(this.condition);
+
+  bool bazz() => condition;
+
+  void bar() {
+    while (bazz() || bazz()) {
+      Expect.fail('Should not reach here');
+    }
+    Expect.fail('Should throw earlier');
+  }
+}
+
+void test3(bool condition) {
+  Test3 obj = new Test3(condition);
+  obj.bar();
+}
+
+const bool test4Condition = null;
+
+void test4(bool condition) {
+  if (test4Condition) {
+    Expect.fail('Should not reach here');
+  }
+  Expect.fail('Should throw earlier');
+}
+
+void test5(bool condition) {
+  if (null) {
+    Expect.fail('Should not reach here');
+  }
+  Expect.fail('Should throw earlier');
+}
+
+void testStackTrace(void testCase(bool condition), List<int> lineNumbers) {
+  try {
+    testCase(null);
+    Expect.fail("Using null in a bool condition should throw TypeError");
+  } catch (e, stacktrace) {
+    print('--------- exception ---------');
+    print(e);
+    print('-------- stack trace --------');
+    print(stacktrace);
+    print('-----------------------------');
+
+    Expect.isTrue(e is AssertionError);
+    Expect.equals(
+        'Failed assertion: boolean expression must not be null', e.toString());
+
+    final String st = stacktrace.toString();
+    for (int lineNum in lineNumbers) {
+      String item = '.dart:$lineNum';
+      Expect.isTrue(st.contains(item), "Stack trace doesn't contain $item");
+    }
+    print('OK');
+  }
+}
+
+main() {
+  testStackTrace(test1, [9, 18]);
+  testStackTrace(test2, [26, 35]);
+  testStackTrace(test3, [45, 54]);
+  testStackTrace(test4, [60]); //# 01: ok
+  testStackTrace(test5, [67]); //# 02: ok
+}