1// Copyright 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/tools/profile_reset/jtl_compiler.h"
6
7#include <limits>
8#include <map>
9#include <numeric>
10
11#include "base/logging.h"
12#include "chrome/browser/profile_resetter/jtl_foundation.h"
13#include "chrome/tools/profile_reset/jtl_parser.h"
14
15namespace jtl = jtl_foundation;
16
17namespace {
18
19// Serializes symbols into byte-code in a streaming manner.
20class ByteCodeWriter {
21 public:
22  explicit ByteCodeWriter(std::string* output) : output_(output) {}
23  ~ByteCodeWriter() {}
24
25  void WriteUint8(uint8 value) { output_->push_back(static_cast<char>(value)); }
26  void WriteUint32(uint32 value) {
27    for (int i = 0; i < 4; ++i) {
28      output_->push_back(static_cast<char>(value & 0xFFu));
29      value >>= 8;
30    }
31  }
32  void WriteOpCode(uint8 op_code) { WriteUint8(op_code); }
33  void WriteHash(const std::string& hash) {
34    CHECK(jtl::Hasher::IsHash(hash));
35    *output_ += hash;
36  }
37  void WriteBool(bool value) { WriteUint8(value ? 1u : 0u); }
38
39 private:
40  std::string* output_;
41
42  DISALLOW_COPY_AND_ASSIGN(ByteCodeWriter);
43};
44
45// Encapsulates meta-data about all instructions, and is capable of transcoding
46// each instruction from a parsed text-based format to byte-code.
47class InstructionSet {
48 public:
49  InstructionSet() {
50    // Define each instruction in this list.
51    // Note:
52    //  - Instructions ending in "hash" will write their 'HashString' arguments
53    //    directly into the byte-code.
54    //  - Instructions ending in "hashed" will first hash their 'String'
55    //    arguments, and will write this hash to the byte-code.
56    Add(Instruction("go", jtl::NAVIGATE, Arguments(String)));
57    Add(Instruction("any", jtl::NAVIGATE_ANY, Arguments()));
58    Add(Instruction("back", jtl::NAVIGATE_BACK, Arguments()));
59    Add(Instruction("store_bool", jtl::STORE_BOOL, Arguments(String, Bool)));
60    Add(Instruction("store_hash",
61                    jtl::STORE_HASH, Arguments(String, HashString)));
62    Add(Instruction("store_hashed",
63                    jtl::STORE_HASH, Arguments(String, String)));
64    Add(Instruction("store_node_bool",
65                    jtl::STORE_NODE_BOOL, Arguments(String)));
66    Add(Instruction("store_node_hash",
67                    jtl::STORE_NODE_HASH, Arguments(String)));
68    Add(Instruction("store_node_registerable_domain_hash",
69                    jtl::STORE_NODE_REGISTERABLE_DOMAIN_HASH,
70                    Arguments(String)));
71    Add(Instruction("compare_bool", jtl::COMPARE_NODE_BOOL, Arguments(Bool)));
72    Add(Instruction("compare_hashed",
73                    jtl::COMPARE_NODE_HASH, Arguments(String)));
74    Add(Instruction("compare_hashed_not",
75                    jtl::COMPARE_NODE_HASH_NOT, Arguments(String)));
76    Add(Instruction("compare_stored_bool",
77                    jtl::COMPARE_STORED_BOOL,
78                    Arguments(String, Bool, Bool)));
79    Add(Instruction("compare_stored_hashed",
80                    jtl::COMPARE_STORED_HASH,
81                    Arguments(String, String, String)));
82    Add(Instruction("compare_to_stored_bool",
83                    jtl::COMPARE_NODE_TO_STORED_BOOL,
84                    Arguments(String)));
85    Add(Instruction("compare_to_stored_hash",
86                    jtl::COMPARE_NODE_TO_STORED_HASH,
87                    Arguments(String)));
88    Add(Instruction("compare_substring_hashed",
89                    jtl::COMPARE_NODE_SUBSTRING,
90                    Arguments(StringPattern)));
91    Add(Instruction("break", jtl::STOP_EXECUTING_SENTENCE, Arguments()));
92  }
93
94  JtlCompiler::CompileError::ErrorCode TranscodeInstruction(
95      const std::string& name,
96      const base::ListValue& arguments,
97      bool ends_sentence,
98      const jtl::Hasher& hasher,
99      ByteCodeWriter* target) const {
100    if (instruction_map_.count(name) == 0)
101      return JtlCompiler::CompileError::INVALID_OPERATION_NAME;
102    const Instruction& instruction(instruction_map_.at(name));
103    if (instruction.argument_types.size() != arguments.GetSize())
104      return JtlCompiler::CompileError::INVALID_ARGUMENT_COUNT;
105    target->WriteOpCode(instruction.op_code);
106    for (size_t i = 0; i < arguments.GetSize(); ++i) {
107      switch (instruction.argument_types[i]) {
108        case Bool: {
109          bool value = false;
110          if (!arguments.GetBoolean(i, &value))
111            return JtlCompiler::CompileError::INVALID_ARGUMENT_TYPE;
112          target->WriteBool(value);
113          break;
114        }
115        case String: {
116          std::string value;
117          if (!arguments.GetString(i, &value))
118            return JtlCompiler::CompileError::INVALID_ARGUMENT_TYPE;
119          target->WriteHash(hasher.GetHash(value));
120          break;
121        }
122        case StringPattern: {
123          std::string value;
124          if (!arguments.GetString(i, &value))
125            return JtlCompiler::CompileError::INVALID_ARGUMENT_TYPE;
126          if (value.empty() ||
127              value.size() > std::numeric_limits<uint32>::max())
128            return JtlCompiler::CompileError::INVALID_ARGUMENT_VALUE;
129          target->WriteHash(hasher.GetHash(value));
130          target->WriteUint32(static_cast<uint32>(value.size()));
131          uint32 pattern_sum = std::accumulate(
132              value.begin(), value.end(), static_cast<uint32>(0u));
133          target->WriteUint32(pattern_sum);
134          break;
135        }
136        case HashString: {
137          std::string hash_value;
138          if (!arguments.GetString(i, &hash_value) ||
139              !jtl::Hasher::IsHash(hash_value))
140            return JtlCompiler::CompileError::INVALID_ARGUMENT_TYPE;
141          target->WriteHash(hash_value);
142          break;
143        }
144        default:
145          NOTREACHED();
146          return JtlCompiler::CompileError::INVALID_ARGUMENT_TYPE;
147      }
148    }
149    if (ends_sentence)
150      target->WriteOpCode(jtl::END_OF_SENTENCE);
151    return JtlCompiler::CompileError::ERROR_NONE;
152  }
153
154 private:
155  // The possible types of an operation's argument.
156  enum ArgumentType {
157    None,
158    Bool,
159    String,
160    StringPattern,
161    HashString
162  };
163
164  // Encapsulates meta-data about one instruction.
165  struct Instruction {
166    Instruction() : op_code(jtl::END_OF_SENTENCE) {}
167    Instruction(const char* name,
168                jtl_foundation::OpCodes op_code,
169                const std::vector<ArgumentType>& argument_types)
170        : name(name), op_code(op_code), argument_types(argument_types) {}
171
172    std::string name;
173    jtl::OpCodes op_code;
174    std::vector<ArgumentType> argument_types;
175  };
176
177  static std::vector<ArgumentType> Arguments(ArgumentType arg1_type = None,
178                                             ArgumentType arg2_type = None,
179                                             ArgumentType arg3_type = None) {
180    std::vector<ArgumentType> result;
181    if (arg1_type != None)
182      result.push_back(arg1_type);
183    if (arg2_type != None)
184      result.push_back(arg2_type);
185    if (arg3_type != None)
186      result.push_back(arg3_type);
187    return result;
188  }
189
190  void Add(const Instruction& instruction) {
191    instruction_map_[instruction.name] = instruction;
192  }
193
194  std::map<std::string, Instruction> instruction_map_;
195
196  DISALLOW_COPY_AND_ASSIGN(InstructionSet);
197};
198
199}  // namespace
200
201bool JtlCompiler::Compile(const std::string& source_code,
202                          const std::string& hash_seed,
203                          std::string* output_bytecode,
204                          CompileError* error_details) {
205  DCHECK(output_bytecode);
206  InstructionSet instruction_set;
207  ByteCodeWriter bytecode_writer(output_bytecode);
208  jtl::Hasher hasher(hash_seed);
209
210  std::string compacted_source_code;
211  std::vector<size_t> newline_indices;
212  size_t mismatched_quotes_line;
213  if (!JtlParser::RemoveCommentsAndAllWhitespace(source_code,
214                                                 &compacted_source_code,
215                                                 &newline_indices,
216                                                 &mismatched_quotes_line)) {
217    if (error_details) {
218      error_details->context = "";  // No meaningful intra-line context here.
219      error_details->line_number = mismatched_quotes_line;
220      error_details->error_code = CompileError::MISMATCHED_DOUBLE_QUOTES;
221    }
222    return false;
223  }
224
225  JtlParser parser(compacted_source_code, newline_indices);
226  while (!parser.HasFinished()) {
227    std::string operation_name;
228    base::ListValue arguments;
229    bool ends_sentence = false;
230    if (!parser.ParseNextOperation(
231             &operation_name, &arguments, &ends_sentence)) {
232      if (error_details) {
233        error_details->context = parser.GetLastContext();
234        error_details->line_number = parser.GetLastLineNumber();
235        error_details->error_code = CompileError::PARSING_ERROR;
236      }
237      return false;
238    }
239    CompileError::ErrorCode error_code = instruction_set.TranscodeInstruction(
240        operation_name, arguments, ends_sentence, hasher, &bytecode_writer);
241    if (error_code != CompileError::ERROR_NONE) {
242      if (error_details) {
243        error_details->context = parser.GetLastContext();
244        error_details->line_number = parser.GetLastLineNumber();
245        error_details->error_code = error_code;
246      }
247      return false;
248    }
249  }
250
251  return true;
252}
253