• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* Copyright 2017 The TensorFlow Authors. All Rights Reserved.
2 
3 Licensed under the Apache License, Version 2.0 (the "License");
4 you may not use this file except in compliance with the License.
5 You may obtain a copy of the License at
6 
7     http://www.apache.org/licenses/LICENSE-2.0
8 
9 Unless required by applicable law or agreed to in writing, software
10 distributed under the License is distributed on an "AS IS" BASIS,
11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 See the License for the specific language governing permissions and
13 limitations under the License.
14 ==============================================================================*/
15 
16 // This module exports ParseFlagsFromEnvAndDieIfUnknown(), which allows other
17 // modules to parse flags from an environtment variable, or a file named by the
18 // environment variable.
19 
20 #include "tensorflow/compiler/xla/parse_flags_from_env.h"
21 
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25 
26 #include <memory>
27 #include <string>
28 #include <vector>
29 
30 #include "absl/container/flat_hash_map.h"
31 #include "absl/strings/ascii.h"
32 #include "absl/strings/str_format.h"
33 #include "absl/strings/str_join.h"
34 #include "absl/types/span.h"
35 #include "tensorflow/compiler/xla/types.h"
36 #include "tensorflow/core/platform/logging.h"
37 #include "tensorflow/core/util/command_line_flags.h"
38 
39 namespace xla {
40 
41 static const char kWS[] = " \t\r\n";  // whitespace
42 
43 // The following struct represents an argv[]-style array, parsed
44 // from data gleaned from the environment.
45 //
46 // As usual, an anonymous namespace is advisable to avoid
47 // constructor/destructor collisions with other "private" types
48 // in the same named namespace.
49 namespace {
50 
51 // Functor which deletes objects by calling `free`.  Necessary to free strdup'ed
52 // strings created by AppendToEnvArgv.
53 struct FreeDeleter {
operator ()xla::__anonf56e81bf0111::FreeDeleter54   void operator()(char* ptr) { free(ptr); }
55 };
56 
57 struct EnvArgv {
EnvArgvxla::__anonf56e81bf0111::EnvArgv58   EnvArgv() : initialized(false), argc(0) {}
59   bool initialized;         // whether the other fields have been set.
60   int argc;                 // elements used in argv[]
61   std::vector<char*> argv;  // flag arguments parsed from environment string.
62   // saved values from argv[] to avoid leaks
63   std::vector<std::unique_ptr<char, FreeDeleter>> argv_save;
64 };
65 }  // anonymous namespace
66 
67 // Append the string s0[0, .., s0len-1] concatenated with s1[0, .., s1len-1] as
68 // a newly allocated nul-terminated string to the array *a.  If s0==nullptr, a
69 // nullptr is appended without increasing a->argc.
AppendToEnvArgv(const char * s0,size_t s0len,const char * s1,size_t s1len,EnvArgv * a)70 static void AppendToEnvArgv(const char* s0, size_t s0len, const char* s1,
71                             size_t s1len, EnvArgv* a) {
72   if (s0 == nullptr) {
73     a->argv.push_back(nullptr);
74     a->argv_save.push_back(nullptr);
75   } else {
76     std::string s = std::string(s0, s0len) + std::string(s1, s1len);
77     char* str = strdup(s.c_str());
78     a->argv.push_back(str);
79     a->argv_save.emplace_back(str);
80     a->argc++;
81   }
82 }
83 
84 // Like s.find_first_of(x, pos), but return s.size() when find_first_of() would
85 // return std::string::npos.  This avoids if-statements elsewhere.
FindFirstOf(const std::string & s,const char * x,size_t pos)86 static size_t FindFirstOf(const std::string& s, const char* x, size_t pos) {
87   size_t result = s.find_first_of(x, pos);
88   return result == std::string::npos ? s.size() : result;
89 }
90 
91 // Like s.find_first_not_of(x, pos), but return s.size() when
92 // find_first_not_of() would return std::string::npos.  This avoids
93 // if-statements elsewhere.
FindFirstNotOf(const std::string & s,const char * x,size_t pos)94 static size_t FindFirstNotOf(const std::string& s, const char* x, size_t pos) {
95   size_t result = s.find_first_not_of(x, pos);
96   return result == std::string::npos ? s.size() : result;
97 }
98 
99 // Given a string containing flags, parse them into the XLA command line flags.
100 // The parse is best effort, and gives up on the first syntax error.
ParseArgvFromString(const std::string & flag_str,EnvArgv * a)101 static void ParseArgvFromString(const std::string& flag_str, EnvArgv* a) {
102   size_t b = FindFirstNotOf(flag_str, kWS, 0);
103   while (b != flag_str.size() && flag_str[b] == '-') {
104     // b is the index of the start of a flag.
105     // Set e to the index just past the end of the flag.
106     size_t e = b;
107     while (e != flag_str.size() && isascii(flag_str[e]) &&
108            (strchr("-_", flag_str[e]) != nullptr ||
109             absl::ascii_isalnum(flag_str[e]))) {
110       e++;
111     }
112     if (e != flag_str.size() && flag_str[e] == '=' &&
113         e + 1 != flag_str.size() && strchr("'\"", flag_str[e + 1]) != nullptr) {
114       // A flag of the form  --flag="something in double or single quotes"
115       int c;
116       e++;  // point just past '='
117       size_t eflag = e;
118       char quote = flag_str[e];
119       e++;  // point just past quote
120       // Put in value the string with quotes removed.
121       std::string value;
122       for (; e != flag_str.size() && (c = flag_str[e]) != quote; e++) {
123         if (quote == '"' && c == '\\' && e + 1 != flag_str.size()) {
124           // Handle backslash in double quoted strings.  They are literal in
125           // single-quoted strings.
126           e++;
127           c = flag_str[e];
128         }
129         value += c;
130       }
131       if (e != flag_str.size()) {  // skip final " or '
132         e++;
133       }
134       AppendToEnvArgv(flag_str.data() + b, eflag - b, value.data(),
135                       value.size(), a);
136     } else {  // A flag without a quoted value.
137       e = FindFirstOf(flag_str, kWS, e);
138       AppendToEnvArgv(flag_str.data() + b, e - b, "", 0, a);
139     }
140     b = FindFirstNotOf(flag_str, kWS, e);
141   }
142 }
143 
144 // Call ParseArgvFromString(..., a) on a string derived from the setting of the
145 // environment variable `envvar`, or a file it points to.
SetArgvFromEnv(absl::string_view envvar,EnvArgv * a)146 static void SetArgvFromEnv(absl::string_view envvar, EnvArgv* a) {
147   if (!a->initialized) {
148     static const char kDummyArgv[] = "<argv[0]>";
149     AppendToEnvArgv(kDummyArgv, strlen(kDummyArgv), nullptr, 0,
150                     a);  // dummy argv[0]
151     const char* env = getenv(std::string(envvar).c_str());
152     if (env == nullptr || env[0] == '\0') {
153       // nothing
154     } else if (env[strspn(env, kWS)] == '-') {  // flags in env var value
155       ParseArgvFromString(env, a);
156     } else {  // assume it's a file name
157       FILE* fp = fopen(env, "r");
158       if (fp != nullptr) {
159         std::string str;
160         char buf[512];
161         int n;
162         while ((n = fread(buf, 1, sizeof(buf), fp)) > 0) {
163           str.append(buf, n);
164         }
165         fclose(fp);
166         ParseArgvFromString(str, a);
167       } else {
168         LOG(QFATAL)
169             << "Could not open file \"" << env
170             << "\" to read flags for environment variable \"" << envvar
171             << "\".  (We assumed \"" << env
172             << "\" was a file name because it did not start with a \"--\".)";
173       }
174     }
175     AppendToEnvArgv(nullptr, 0, nullptr, 0, a);  // add trailing nullptr to *a.
176     a->initialized = true;
177   }
178 }
179 
180 // The simulated argv[] parsed from the environment, one for each different
181 // environment variable we've seen.
EnvArgvs()182 static absl::flat_hash_map<std::string, EnvArgv>& EnvArgvs() {
183   static auto* env_argvs = new absl::flat_hash_map<std::string, EnvArgv>();
184   return *env_argvs;
185 }
186 
187 // Used to protect accesses to env_argvs.
188 static absl::Mutex env_argv_mu(absl::kConstInit);
189 
ParseFlagsFromEnvAndDieIfUnknown(absl::string_view envvar,const std::vector<tensorflow::Flag> & flag_list)190 bool ParseFlagsFromEnvAndDieIfUnknown(
191     absl::string_view envvar, const std::vector<tensorflow::Flag>& flag_list) {
192   absl::MutexLock lock(&env_argv_mu);
193   auto* env_argv = &EnvArgvs()[std::string(envvar)];
194   SetArgvFromEnv(envvar, env_argv);  // a no-op if already initialized
195 
196   if (VLOG_IS_ON(1)) {
197     VLOG(1) << "For env var " << envvar << " found arguments:";
198     for (int i = 0; i < env_argv->argc; i++) {
199       VLOG(1) << "  argv[" << i << "] = " << env_argv->argv[i];
200     }
201   }
202 
203   bool result =
204       tensorflow::Flags::Parse(&env_argv->argc, &env_argv->argv[0], flag_list);
205 
206   // There's always at least one unparsed argc, namely the fake argv[0].
207   if (result && env_argv->argc != 1) {
208     // Skip the first argv, which is the fake argv[0].
209     auto unknown_flags = absl::MakeSpan(env_argv->argv);
210     unknown_flags.remove_prefix(1);
211 
212     // Some flags are set on XLA_FLAGS, others on TF_XLA_FLAGS.  If we find an
213     // unrecognized flag, suggest the alternative.
214     std::string alternate_envvar;
215     if (envvar == "TF_XLA_FLAGS") {
216       alternate_envvar = "XLA_FLAGS";
217     } else if (envvar == "XLA_FLAGS") {
218       alternate_envvar = "TF_XLA_FLAGS";
219     }
220     std::string did_you_mean;
221     if (!alternate_envvar.empty()) {
222       did_you_mean = absl::StrFormat(
223           "\nPerhaps you meant to specify these on the %s envvar?",
224           alternate_envvar);
225     }
226 
227     LOG(QFATAL) << "Unknown flag" << (unknown_flags.size() > 1 ? "s" : "")
228                 << " in " << envvar << ": " << absl::StrJoin(unknown_flags, " ")
229                 << did_you_mean;
230     return false;
231   }
232   return result;
233 }
234 
235 // Testing only.
236 //
237 // Resets the env_argv struct so that subsequent calls to
238 // ParseFlagsFromEnvAndDieIfUnknown() will parse the environment variable (or
239 // the file it points to) anew, and set *pargc, and *pargv to point to the
240 // internal locations of the argc and argv constructed from the environment.
ResetFlagsFromEnvForTesting(absl::string_view envvar,int ** pargc,std::vector<char * > ** pargv)241 void ResetFlagsFromEnvForTesting(absl::string_view envvar, int** pargc,
242                                  std::vector<char*>** pargv) {
243   absl::MutexLock lock(&env_argv_mu);
244   EnvArgvs().erase(std::string(envvar));
245   auto& env_argv = EnvArgvs()[std::string(envvar)];
246   *pargc = &env_argv.argc;
247   *pargv = &env_argv.argv;
248 }
249 
250 }  // namespace xla
251